diff --git a/Cargo.lock b/Cargo.lock index 13044c7..fc25c72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1113,12 +1113,14 @@ dependencies = [ "dao-hooks", "dao-interface", "dao-pre-propose-base", + "dao-pre-propose-multiple", "dao-voting 0.1.0", "dao-voting 2.4.0", "dao-voting-cw4", "dao-voting-snip20-staked", "dao-voting-snip721-staked", "dao-voting-token-staked", + "query_auth 0.1.0", "rand", "schemars 0.8.16", "secret-cosmwasm-std", diff --git a/contracts/proposal/dao-proposal-multiple/Cargo.toml b/contracts/proposal/dao-proposal-multiple/Cargo.toml index 5d23c5b..edd6a74 100644 --- a/contracts/proposal/dao-proposal-multiple/Cargo.toml +++ b/contracts/proposal/dao-proposal-multiple/Cargo.toml @@ -45,6 +45,7 @@ serde ={ workspace=true } schemars ={ workspace = true } secret-cw-controllers ={ workspace = true } shade-protocol ={ workspace = true } +query_auth ={ workspace = true } [dev-dependencies] anyhow = { workspace = true } @@ -55,6 +56,7 @@ dao-voting-snip20-staked = { workspace = true } dao-voting-token-staked = { workspace = true } dao-voting-snip721-staked = { workspace = true } cw-denom = { workspace = true } +dao-pre-propose-multiple ={ workspace = true } # dao-testing = { workspace = true } snip20-stake = { workspace = true } cw4 = { workspace = true } diff --git a/contracts/proposal/dao-proposal-multiple/src/contract.rs b/contracts/proposal/dao-proposal-multiple/src/contract.rs index 860ef01..df153e5 100644 --- a/contracts/proposal/dao-proposal-multiple/src/contract.rs +++ b/contracts/proposal/dao-proposal-multiple/src/contract.rs @@ -127,13 +127,13 @@ pub fn execute( proposer, ), ExecuteMsg::Vote { - key, + auth, proposal_id, vote, rationale, - } => execute_vote(deps, env, info, key, proposal_id, vote, rationale), - ExecuteMsg::Execute { key, proposal_id } => { - execute_execute(deps, env, info, key, proposal_id) + } => execute_vote(deps, env, info, auth, proposal_id, vote, rationale), + ExecuteMsg::Execute { auth, proposal_id } => { + execute_execute(deps, env, info, auth, proposal_id) } ExecuteMsg::Veto { proposal_id } => execute_veto(deps, env, info, proposal_id), ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id), @@ -377,16 +377,12 @@ pub fn execute_vote( deps: DepsMut, env: Env, info: MessageInfo, - key: String, + auth: Auth, proposal_id: u64, vote: MultipleChoiceVote, rationale: Option, ) -> Result, ContractError> { let dao_info = DAO.load(deps.storage)?; - let auth = Auth::ViewingKey { - key, - address: info.sender.clone().to_string(), - }; let mut prop = PROPOSALS .get(deps.storage, &proposal_id) .ok_or(ContractError::NoSuchProposal { id: proposal_id })?; @@ -493,7 +489,7 @@ pub fn execute_execute( deps: DepsMut, env: Env, info: MessageInfo, - key: String, + auth: Auth, proposal_id: u64, ) -> Result { let mut prop = PROPOSALS @@ -502,10 +498,6 @@ pub fn execute_execute( let config = CONFIG.load(deps.storage)?; let dao_info = DAO.load(deps.storage)?; - let auth = Auth::ViewingKey { - key, - address: info.sender.clone().to_string(), - }; // determine if this sender can execute let mut sender_can_execute = true; diff --git a/contracts/proposal/dao-proposal-multiple/src/lib.rs b/contracts/proposal/dao-proposal-multiple/src/lib.rs index 72850b7..9841649 100644 --- a/contracts/proposal/dao-proposal-multiple/src/lib.rs +++ b/contracts/proposal/dao-proposal-multiple/src/lib.rs @@ -8,5 +8,5 @@ pub mod query; pub mod state; pub use crate::error::ContractError; -// [cfg(test)] -// // pub mod testing; +#[cfg(test)] +pub mod testing; diff --git a/contracts/proposal/dao-proposal-multiple/src/msg.rs b/contracts/proposal/dao-proposal-multiple/src/msg.rs index 8adc95e..fda3ef6 100644 --- a/contracts/proposal/dao-proposal-multiple/src/msg.rs +++ b/contracts/proposal/dao-proposal-multiple/src/msg.rs @@ -72,8 +72,7 @@ pub enum ExecuteMsg { /// Votes on a proposal. Voting power is determined by the DAO's /// voting power module. Vote { - /// Viewng key of the sender - key: String, + auth: Auth, /// The ID of the proposal to vote on. proposal_id: u64, /// The senders position on the proposal. @@ -86,8 +85,7 @@ pub enum ExecuteMsg { /// Causes the messages associated with a passed proposal to be /// executed by the DAO. Execute { - /// Viewng key of the sender - key: String, + auth: Auth, /// The ID of the proposal to execute. proposal_id: u64, }, diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs deleted file mode 100644 index d854999..0000000 --- a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs +++ /dev/null @@ -1,401 +0,0 @@ -use crate::msg::{ExecuteMsg, InstantiateMsg}; -use crate::testing::execute::{make_proposal, mint_cw20s}; -use crate::testing::instantiate::{ - _get_default_token_dao_proposal_module_instantiate, - instantiate_with_multiple_staked_balances_governance, -}; -use crate::testing::queries::{ - query_balance_cw20, query_dao_token, query_multiple_proposal_module, query_proposal, -}; -use crate::testing::tests::{get_pre_propose_info, ALTERNATIVE_ADDR, CREATOR_ADDR}; -use crate::ContractError; -use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, Decimal, Uint128, WasmMsg}; -use cw20::Cw20Coin; -use cw_multi_test::{next_block, App, Executor}; -use cw_utils::Duration; -use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, - multiple_choice::{ - MultipleChoiceOption, MultipleChoiceOptions, MultipleChoiceVote, VotingStrategy, - }, - status::Status, - threshold::PercentageThreshold, -}; - -struct CommonTest { - app: App, - proposal_module: Addr, - proposal_id: u64, -} -fn setup_test(_messages: Vec) -> CommonTest { - let mut app = App::default(); - let instantiate = _get_default_token_dao_proposal_module_instantiate(&mut app); - let core_addr = - instantiate_with_multiple_staked_balances_governance(&mut app, instantiate, None); - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - // Mint some tokens to pay the proposal deposit. - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - - let options = vec![ - MultipleChoiceOption { - title: "title 1".to_string(), - description: "multiple choice option 1".to_string(), - msgs: vec![], - }, - MultipleChoiceOption { - title: "title 2".to_string(), - description: "multiple choice option 2".to_string(), - msgs: vec![], - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, mc_options); - - CommonTest { - app, - proposal_module, - proposal_id, - } -} - -// A proposal that is still accepting votes (is open) cannot -// be executed. Any attempts to do so should fail and return -// an error. -#[test] -fn test_execute_proposal_open() { - let CommonTest { - mut app, - proposal_module, - proposal_id, - } = setup_test(vec![]); - - app.update_block(next_block); - - // assert proposal is open - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Open); - - // attempt to execute and assert that it fails - let err = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module, - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert!(matches!(err, ContractError::NotPassed {})) -} - -// A proposal can be executed if and only if it passed. -// Any attempts to execute a proposal that has been rejected -// or closed (after rejection) should fail and return an error. -#[test] -fn test_execute_proposal_rejected_closed() { - let CommonTest { - mut app, - proposal_module, - proposal_id, - } = setup_test(vec![]); - - app.update_block(next_block); - - // assert proposal is open - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Open); - - // Vote on both options to reject the proposal - let vote = MultipleChoiceVote { option_id: 0 }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id, - vote, - rationale: None, - }, - &[], - ) - .unwrap(); - - let vote = MultipleChoiceVote { option_id: 1 }; - app.execute_contract( - Addr::unchecked(ALTERNATIVE_ADDR), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id, - vote, - rationale: None, - }, - &[], - ) - .unwrap(); - - app.update_block(next_block); - - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Rejected); - - // attempt to execute and assert that it fails - let err = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert!(matches!(err, ContractError::NotPassed {})); - - app.update_block(next_block); - - // close the proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Close { proposal_id }, - &[], - ) - .unwrap(); - - // assert prop is closed and attempt to execute it - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Closed); - - let err = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module, - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::NotPassed {})); -} - -// A proposal can only be executed once. Any subsequent -// attempts to execute it should fail and return an error. -#[test] -fn test_execute_proposal_more_than_once() { - let CommonTest { - mut app, - proposal_module, - proposal_id, - } = setup_test(vec![]); - - app.update_block(next_block); - - // get the proposal to pass - let vote = MultipleChoiceVote { option_id: 0 }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id, - vote, - rationale: None, - }, - &[], - ) - .unwrap(); - app.execute_contract( - Addr::unchecked(ALTERNATIVE_ADDR), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id, - vote, - rationale: None, - }, - &[], - ) - .unwrap(); - - app.update_block(next_block); - - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Passed); - - // execute the proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap(); - - app.update_block(next_block); - - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Executed); - - let err = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module, - &ExecuteMsg::Execute { proposal_id }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert!(matches!(err, ContractError::NotPassed {})); -} - -// Users should be able to submit votes past the proposal -// expiration date. Such votes do not affect the outcome -// of the proposals; instead, they are meant to allow -// voters to voice their opinion. -#[test] -pub fn test_allow_voting_after_proposal_execution_pre_expiration_cw20() { - let mut app = App::default(); - - let instantiate = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(66)), - }, - max_voting_period: Duration::Time(604800), - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(10_000_000), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - close_proposal_on_execution_failure: true, - veto: None, - }; - - let core_addr = instantiate_with_multiple_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: ALTERNATIVE_ADDR.to_string(), - amount: Uint128::new(50_000_000), - }, - ]), - ); - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - let gov_token = query_dao_token(&app, &core_addr); - - // Mint some tokens to pay the proposal deposit. - mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - - // Option 0 would mint 100_000_000 tokens for CREATOR_ADDR - let msg = cw20::Cw20ExecuteMsg::Mint { - recipient: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }; - let binary_msg = to_json_binary(&msg).unwrap(); - - let options = vec![ - MultipleChoiceOption { - title: "title 1".to_string(), - description: "multiple choice option 1".to_string(), - msgs: vec![WasmMsg::Execute { - contract_addr: gov_token.to_string(), - msg: binary_msg, - funds: vec![], - } - .into()], - }, - MultipleChoiceOption { - title: "title 2".to_string(), - description: "multiple choice option 2".to_string(), - msgs: vec![], - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, mc_options); - - // assert initial CREATOR_ADDR address balance is 0 - let balance = query_balance_cw20(&app, gov_token.to_string(), CREATOR_ADDR); - assert_eq!(balance, Uint128::zero()); - - app.update_block(next_block); - - let vote = MultipleChoiceVote { option_id: 0 }; - - // someone votes enough to pass the proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id, - vote, - rationale: None, - }, - &[], - ) - .unwrap(); - - app.update_block(next_block); - - // assert proposal is passed with expected votes - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Passed); - assert_eq!(prop.proposal.votes.vote_weights[0], Uint128::new(100000000)); - assert_eq!(prop.proposal.votes.vote_weights[1], Uint128::new(0)); - - // someone wakes up and casts their vote to express their - // opinion (not affecting the result of proposal) - let vote = MultipleChoiceVote { option_id: 1 }; - app.execute_contract( - Addr::unchecked(ALTERNATIVE_ADDR), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id, - vote, - rationale: None, - }, - &[], - ) - .unwrap(); - - app.update_block(next_block); - - // assert proposal is passed with expected votes - let prop = query_proposal(&app, &proposal_module, proposal_id); - assert_eq!(prop.proposal.status, Status::Passed); - assert_eq!(prop.proposal.votes.vote_weights[0], Uint128::new(100000000)); - assert_eq!(prop.proposal.votes.vote_weights[1], Uint128::new(50000000)); - - // execute the proposal expecting - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // assert option 0 message executed as expected changed as expected - let balance = query_balance_cw20(&app, gov_token.to_string(), CREATOR_ADDR); - assert_eq!(balance, Uint128::new(110_000_000)); -} diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/contracts.rs b/contracts/proposal/dao-proposal-multiple/src/testing/contracts.rs new file mode 100644 index 0000000..0b6ccdb --- /dev/null +++ b/contracts/proposal/dao-proposal-multiple/src/testing/contracts.rs @@ -0,0 +1,22 @@ +use cosmwasm_std::Empty; +use secret_multi_test::{Contract, ContractWrapper}; + +pub(crate) fn proposal_single_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) +} + +pub(crate) fn query_auth_contract() -> Box> { + let contract = ContractWrapper::new( + query_auth::contract::execute, + query_auth::contract::instantiate, + query_auth::contract::query, + ); + Box::new(contract) +} diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs deleted file mode 100644 index e8b5744..0000000 --- a/contracts/proposal/dao-proposal-multiple/src/testing/do_votes.rs +++ /dev/null @@ -1,842 +0,0 @@ -use cosmwasm_std::{coins, Addr, Decimal, Uint128}; -use cw20::Cw20Coin; -use cw_denom::CheckedDenom; -use cw_multi_test::{App, BankSudo, Executor}; -use dao_interface::state::ProposalModule; -use dao_testing::ShouldExecute; -use dao_voting::{ - deposit::{CheckedDepositInfo, UncheckedDepositInfo}, - multiple_choice::{ - MultipleChoiceOption, MultipleChoiceOptions, MultipleChoiceVote, VotingStrategy, - }, - status::Status, - threshold::PercentageThreshold, -}; -use rand::{prelude::SliceRandom, Rng}; -use std::panic; - -use crate::{ - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - query::{ProposalResponse, VoteInfo, VoteResponse}, - testing::{ - instantiate::{ - instantiate_with_cw20_balances_governance, instantiate_with_staked_balances_governance, - }, - queries::query_deposit_config_and_pre_propose_module, - tests::{get_pre_propose_info, proposal_multiple_contract, TestMultipleChoiceVote}, - }, -}; -use dao_pre_propose_multiple as cppm; - -fn do_votes_cw20_balances( - votes: Vec, - voting_strategy: VotingStrategy, - expected_status: Status, - total_supply: Option, - should_expire: bool, -) { - do_test_votes( - votes, - voting_strategy, - expected_status, - total_supply, - None::, - should_expire, - instantiate_with_staked_balances_governance, - ); -} - -fn do_votes_staked_balances( - votes: Vec, - voting_strategy: VotingStrategy, - expected_status: Status, - total_supply: Option, - should_expire: bool, -) { - do_test_votes( - votes, - voting_strategy, - expected_status, - total_supply, - None::, - should_expire, - instantiate_with_staked_balances_governance, - ); -} - -fn do_votes_cw4_weights( - votes: Vec, - voting_strategy: VotingStrategy, - expected_status: Status, - total_supply: Option, - should_expire: bool, -) { - do_test_votes( - votes, - voting_strategy, - expected_status, - total_supply, - None::, - should_expire, - instantiate_with_staked_balances_governance, - ); -} - -// Creates multiple choice proposal with provided config and executes provided votes against it. -fn do_test_votes( - votes: Vec, - voting_strategy: VotingStrategy, - expected_status: Status, - total_supply: Option, - deposit_info: Option, - should_expire: bool, - setup_governance: F, -) -> (App, Addr) -where - F: Fn(&mut App, InstantiateMsg, Option>) -> Addr, -{ - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let mut initial_balances = votes - .iter() - .map(|TestMultipleChoiceVote { voter, weight, .. }| Cw20Coin { - address: voter.to_string(), - amount: *weight, - }) - .collect::>(); - let initial_balances_supply = votes.iter().fold(Uint128::zero(), |p, n| p + n.weight); - let to_fill = total_supply.map(|total_supply| total_supply - initial_balances_supply); - if let Some(fill) = to_fill { - initial_balances.push(Cw20Coin { - address: "filler".to_string(), - amount: fill, - }) - } - - let pre_propose_info = get_pre_propose_info(&mut app, deposit_info, false); - - let proposer = match votes.first() { - Some(vote) => vote.voter.clone(), - None => panic!("do_test_votes must have at least one vote."), - }; - - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - close_proposal_on_execution_failure: true, - pre_propose_info, - veto: None, - }; - - let governance_addr = setup_governance(&mut app, instantiate, Some(initial_balances)); - - let governance_modules: Vec = app - .wrap() - .query_wasm_smart( - governance_addr.clone(), - &dao_interface::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - // Allow a proposal deposit as needed. - let (deposit_config, pre_propose_module) = - query_deposit_config_and_pre_propose_module(&app, &govmod); - - // Increase allowance to pay the cw20 deposit if needed. - if let Some(CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - amount, - .. - }) = deposit_config.deposit_info - { - app.execute_contract( - Addr::unchecked(&proposer), - token.clone(), - &cw20_base::msg::ExecuteMsg::IncreaseAllowance { - spender: pre_propose_module.to_string(), - amount, - expires: None, - }, - &[], - ) - .unwrap(); - } - - let funds = if let Some(CheckedDepositInfo { - denom: CheckedDenom::Native(ref denom), - amount, - .. - }) = deposit_config.deposit_info - { - // Mint the needed tokens to create the deposit. - app.sudo(cw_multi_test::SudoMsg::Bank(BankSudo::Mint { - to_address: proposer.clone(), - amount: coins(amount.u128(), denom), - })) - .unwrap(); - coins(amount.u128(), denom) - } else { - vec![] - }; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - app.execute_contract( - Addr::unchecked(&proposer), - pre_propose_module, - &cppm::ExecuteMsg::Propose { - msg: cppm::ProposeMessage::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - choices: mc_options, - }, - }, - &funds, - ) - .unwrap(); - - // Cast votes. - for vote in votes { - let TestMultipleChoiceVote { - voter, - position, - weight, - should_execute, - } = vote; - // Vote on the proposal. - let res = app.execute_contract( - Addr::unchecked(voter.clone()), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: position, - rationale: None, - }, - &[], - ); - match should_execute { - ShouldExecute::Yes => { - if res.is_err() { - panic!() - } - // Check that the vote was recorded correctly. - let vote: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod.clone(), - &QueryMsg::GetVote { - proposal_id: 1, - voter: voter.clone(), - }, - ) - .unwrap(); - let expected = VoteResponse { - vote: Some(VoteInfo { - voter: Addr::unchecked(&voter), - vote: position, - power: match deposit_config.deposit_info { - Some(CheckedDepositInfo { - amount, - denom: CheckedDenom::Cw20(_), - .. - }) => { - if proposer == voter { - weight - amount - } else { - weight - } - } - // Native token deposits shouldn't impact - // expected voting power. - _ => weight, - }, - rationale: None, - }), - }; - assert_eq!(vote, expected) - } - ShouldExecute::No => { - res.unwrap_err(); - } - ShouldExecute::Meh => (), - } - } - - // Expire the proposal if this is expected. - if should_expire { - app.update_block(|block| block.height += 100); - } - - let proposal: ProposalResponse = app - .wrap() - .query_wasm_smart(govmod, &QueryMsg::Proposal { proposal_id: 1 }) - .unwrap(); - - assert_eq!(proposal.proposal.status, expected_status); - - (app, governance_addr) -} - -// Creates a proposal and then executes a series of votes on those -// proposals. Asserts both that those votes execute as expected and -// that the final status of the proposal is what is expected. Returns -// the address of the governance contract that it has created so that -// callers may do additional inspection of the contract's state. -pub fn do_test_votes_cw20_balances( - votes: Vec, - voting_strategy: VotingStrategy, - expected_status: Status, - total_supply: Option, - deposit_info: Option, - should_expire: bool, -) -> (App, Addr) { - do_test_votes( - votes, - voting_strategy, - expected_status, - total_supply, - deposit_info, - should_expire, - instantiate_with_cw20_balances_governance, - ) -} - -pub fn test_simple_votes(do_test_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - // Vote for one option, passes - do_test_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - Status::Passed, - None, - false, - ); - - // Vote for none of the above, gets rejected - do_test_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - Status::Rejected, - None, - false, - ) -} - -pub fn test_vote_invalid_option(do_test_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - // Vote for out of bounds option - do_test_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 10 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::No, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - Status::Open, - None, - false, - ); -} - -pub fn test_vote_no_overflow(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(u128::max_value()), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - Status::Passed, - None, - false, - ); - - do_votes( - vec![ - TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }, - TestMultipleChoiceVote { - voter: "bob".to_string(), - position: MultipleChoiceVote { option_id: 1 }, - weight: Uint128::new(u128::max_value() - 1), - should_execute: ShouldExecute::Yes, - }, - ], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - Status::Passed, - None, - false, - ); -} - -pub fn test_vote_tied_rejected(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - do_votes( - vec![ - TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }, - TestMultipleChoiceVote { - voter: "bob".to_string(), - position: MultipleChoiceVote { option_id: 1 }, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }, - ], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - Status::Rejected, - None, - false, - ); -} - -pub fn test_vote_none_of_the_above_only(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 2 }, // the last index is none of the above - weight: Uint128::new(u64::max_value().into()), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - Status::Rejected, - None, - false, - ); - - for i in 0..101 { - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(u64::max_value().into()), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(i)), - }, - Status::Rejected, - None, - false, - ); - } -} - -pub fn test_tricky_rounding(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - // This tests the smallest possible round up for passing - // thresholds we can have. Specifically, a 1% passing threshold - // and 1 total vote. This should round up and only pass if there - // are 1 or more yes votes. - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(1)), - }, - Status::Passed, - Some(Uint128::new(100)), - true, - ); - - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(1)), - }, - Status::Passed, - Some(Uint128::new(1000)), - true, - ); - - // High Precision - // Proposal should be rejected if < 1% have voted and proposal expires - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 1 }, - weight: Uint128::new(9999999), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(1)), - }, - Status::Rejected, - Some(Uint128::new(1000000000)), - true, - ); - - // Proposal should be rejected if quorum is met but "none of the above" is the winning option. - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(1)), - }, - Status::Rejected, - None, - false, - ); -} - -pub fn test_no_double_votes(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - do_votes( - vec![ - TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 1 }, - weight: Uint128::new(2), - should_execute: ShouldExecute::Yes, - }, - TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 1 }, - weight: Uint128::new(2), - should_execute: ShouldExecute::No, - }, - ], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - // NOTE: Updating our cw20-base version will cause this to - // fail. In versions of cw20-base before Feb 15 2022 (the one - // we use at the time of writing) it was allowed to have an - // initial balance that repeats for a given address but it - // would cause miscalculation of the total supply. In this - // case the total supply is miscomputed to be 4 so this is - // assumed to have 2 abstain votes out of 4 possible votes. - Status::Open, - Some(Uint128::new(10)), - false, - ) -} - -pub fn test_majority_vs_half(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - // Half - do_votes( - vec![ - TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }, - TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }, - ], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(50)), - }, - Status::Passed, - Some(Uint128::new(40)), - true, - ); - - // Majority - do_votes( - vec![ - TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }, - TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }, - ], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Rejected, - Some(Uint128::new(40)), - true, - ); -} - -pub fn test_pass_exactly_quorum(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(60), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(60)), - }, - Status::Passed, - Some(Uint128::new(100)), - false, - ); - - // None of the above wins - do_votes( - vec![TestMultipleChoiceVote { - voter: "bluenote".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(60), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(60)), - }, - Status::Rejected, - Some(Uint128::new(100)), - false, - ); -} - -pub fn fuzz_voting(do_votes: F) -where - F: Fn(Vec, VotingStrategy, Status, Option, bool), -{ - let mut rng = rand::thread_rng(); - let dist = rand::distributions::Uniform::::new(1, 200); - for _ in 0..10 { - let zero: Vec = (0..50).map(|_| rng.sample(dist)).collect(); - let one: Vec = (0..50).map(|_| rng.sample(dist)).collect(); - let none: Vec = (0..50).map(|_| rng.sample(dist)).collect(); - - let zero_sum: u64 = zero.iter().sum(); - let one_sum: u64 = one.iter().sum(); - let none_sum: u64 = none.iter().sum(); - - let mut sums = [zero_sum, one_sum, none_sum]; - sums.sort_unstable(); - - // If none of the above wins or there is a tie between second and first choice. - let expected_status: Status = if *sums.last().unwrap() == none_sum || sums[1] == sums[2] { - Status::Rejected - } else { - Status::Passed - }; - - let zero = zero - .into_iter() - .enumerate() - .map(|(idx, weight)| TestMultipleChoiceVote { - voter: format!("zero_{idx}"), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(weight as u128), - should_execute: ShouldExecute::Meh, - }); - let one = one - .into_iter() - .enumerate() - .map(|(idx, weight)| TestMultipleChoiceVote { - voter: format!("one_{idx}"), - position: MultipleChoiceVote { option_id: 1 }, - weight: Uint128::new(weight as u128), - should_execute: ShouldExecute::Meh, - }); - - let none = none - .into_iter() - .enumerate() - .map(|(idx, weight)| TestMultipleChoiceVote { - voter: format!("none_{idx}"), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(weight as u128), - should_execute: ShouldExecute::Meh, - }); - - let mut votes = zero.chain(one).chain(none).collect::>(); - votes.shuffle(&mut rng); - - do_votes( - votes, - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - expected_status, - None, - true, - ); - } -} - -#[test] -fn test_vote_simple() { - test_simple_votes(do_votes_cw20_balances); - test_simple_votes(do_votes_cw4_weights); - test_simple_votes(do_votes_staked_balances) -} - -#[test] -fn test_vote_out_of_bounds() { - test_vote_invalid_option(do_votes_cw20_balances); - test_vote_invalid_option(do_votes_cw4_weights); - test_vote_invalid_option(do_votes_staked_balances); -} - -#[test] -fn test_no_overflow() { - test_vote_no_overflow(do_votes_cw20_balances); - test_vote_no_overflow(do_votes_staked_balances); - test_vote_no_overflow(do_votes_cw4_weights) -} - -#[test] -fn test_quorum_not_met() { - test_vote_no_overflow(do_votes_cw20_balances); - test_vote_no_overflow(do_votes_staked_balances); - test_vote_no_overflow(do_votes_cw4_weights) -} - -#[test] -fn test_votes_tied() { - test_vote_tied_rejected(do_votes_cw20_balances); - test_vote_tied_rejected(do_votes_staked_balances); - test_vote_tied_rejected(do_votes_cw4_weights) -} - -#[test] -fn test_votes_none_of_the_above() { - test_vote_none_of_the_above_only(do_votes_cw20_balances); - test_vote_none_of_the_above_only(do_votes_staked_balances); - test_vote_none_of_the_above_only(do_votes_cw4_weights) -} - -#[test] -fn test_rounding() { - test_tricky_rounding(do_votes_cw20_balances); - test_tricky_rounding(do_votes_staked_balances); - test_tricky_rounding(do_votes_cw4_weights) -} - -#[test] -fn test_no_double_vote() { - test_no_double_votes(do_votes_cw20_balances); - test_no_double_votes(do_votes_staked_balances); - test_no_double_votes(do_votes_cw4_weights) -} - -#[test] -fn test_majority_half() { - test_majority_vs_half(do_votes_cw20_balances); - test_majority_vs_half(do_votes_staked_balances); - test_majority_vs_half(do_votes_cw4_weights) -} - -#[test] -fn test_pass_exact_quorum() { - test_pass_exactly_quorum(do_votes_cw20_balances); - test_pass_exactly_quorum(do_votes_staked_balances); - test_pass_exactly_quorum(do_votes_cw4_weights) -} - -#[test] -fn fuzz_votes_cw20_balances() { - fuzz_voting(do_votes_cw20_balances) -} - -#[test] -fn fuzz_votes_cw4_weights() { - fuzz_voting(do_votes_cw4_weights) -} - -#[test] -fn fuzz_votes_staked_balances() { - fuzz_voting(do_votes_staked_balances) -} diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs b/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs index bc5168e..6846888 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs @@ -1,132 +1,637 @@ -use cosmwasm_std::{coins, Addr, Uint128}; -use cw_multi_test::{App, Executor}; - -use cw_denom::CheckedDenom; -use dao_pre_propose_multiple as cppm; -use dao_voting::{ - deposit::CheckedDepositInfo, multiple_choice::MultipleChoiceOptions, - pre_propose::ProposalCreationPolicy, +use cosmwasm_std::{from_binary, Addr, ContractInfo, CosmosMsg, MessageInfo}; +use secret_multi_test::{App, Executor}; + +use dao_voting::multiple_choice::{ + MultipleChoiceOption, MultipleChoiceOptions, MultipleChoiceVote, }; +use secret_utils::Duration; +use shade_protocol::{basic_staking::Auth, utils::asset::RawContract}; use crate::{ msg::{ExecuteMsg, QueryMsg}, query::ProposalResponse, - testing::queries::{query_creation_policy, query_pre_proposal_multiple_config}, + testing::queries::query_next_proposal_id, + ContractError, }; // Creates a proposal then checks that the proposal was created with // the specified messages and returns the ID of the proposal. -// -// This expects that the proposer already has the needed tokens to pay -// the deposit. -pub fn make_proposal( + +pub(crate) fn make_proposal( app: &mut App, proposal_multiple: &Addr, - proposer: &str, - choices: MultipleChoiceOptions, -) -> u64 { - let proposal_creation_policy = query_creation_policy(app, proposal_multiple); - - // Collect the funding. - let funds = match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => vec![], - ProposalCreationPolicy::Module { - addr: ref pre_propose, - } => { - let deposit_config = query_pre_proposal_multiple_config(app, pre_propose); - match deposit_config.deposit_info { - Some(CheckedDepositInfo { - denom, - amount, - refund_policy: _, - }) => match denom { - CheckedDenom::Native(denom) => coins(amount.u128(), denom), - CheckedDenom::Cw20(addr) => { - // Give an allowance, no funds. - app.execute_contract( - Addr::unchecked(proposer), - addr, - &cw20::Cw20ExecuteMsg::IncreaseAllowance { - spender: pre_propose.to_string(), - amount, - expires: None, - }, - &[], - ) - .unwrap(); - vec![] - } - }, - None => vec![], - } + proposal_multiple_code_hash: String, + auth: Auth, + msgs: Vec, +) { + let mut proposer = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + proposer = Addr::unchecked(address); } - }; + _ => (), + } - // Make the proposal. - match proposal_creation_policy { - ProposalCreationPolicy::Anyone {} => app - .execute_contract( - Addr::unchecked(proposer), - proposal_multiple.clone(), - &ExecuteMsg::Propose { - title: "title".to_string(), - description: "description".to_string(), - choices, - proposer: None, - }, - &[], - ) - .unwrap(), - ProposalCreationPolicy::Module { addr } => app - .execute_contract( - Addr::unchecked(proposer), - addr, - &cppm::ExecuteMsg::Propose { - msg: cppm::ProposeMessage::Propose { - title: "title".to_string(), - description: "description".to_string(), - choices, - }, - }, - &funds, - ) - .unwrap(), - }; + app.execute_contract( + Addr::unchecked(proposer.clone()), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash.clone(), + }, + &ExecuteMsg::Propose { + title: "title".to_string(), + description: "description".to_string(), + proposer: None, + choices: MultipleChoiceOptions { + options: vec![MultipleChoiceOption { + title: "title1".to_string(), + description: "description1".to_string(), + msgs, + }], + }, + }, + &[], + ) + .unwrap(); - let id: u64 = app - .wrap() - .query_wasm_smart(proposal_multiple, &QueryMsg::NextProposalId {}) - .unwrap(); + let id = query_next_proposal_id(app, proposal_multiple, proposal_multiple_code_hash.clone()); let id = id - 1; // Check that the proposal was created as expected. let proposal: ProposalResponse = app .wrap() - .query_wasm_smart(proposal_multiple, &QueryMsg::Proposal { proposal_id: id }) + .query_wasm_smart( + proposal_multiple_code_hash, + proposal_multiple, + &QueryMsg::Proposal { proposal_id: id }, + ) .unwrap(); assert_eq!(proposal.proposal.proposer, Addr::unchecked(proposer)); assert_eq!(proposal.proposal.title, "title".to_string()); assert_eq!(proposal.proposal.description, "description".to_string()); +} + +pub(crate) fn _vote_on_proposal( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + auth: Auth, + proposal_id: u64, + vote: MultipleChoiceVote, +) { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash.clone(), + }, + &ExecuteMsg::Vote { + auth, + proposal_id, + vote, + rationale: None, + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn vote_on_proposal_should_fail( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + auth: Auth, + proposal_id: u64, + vote: MultipleChoiceVote, +) -> ContractError { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::Vote { + auth, + proposal_id, + vote, + rationale: None, + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn execute_proposal_should_fail( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + auth: Auth, + proposal_id: u64, +) -> ContractError { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::Execute { auth, proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn _vote_on_proposal_with_rationale( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + auth: Auth, + proposal_id: u64, + vote: MultipleChoiceVote, + rationale: Option, +) { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::Vote { + auth, + proposal_id, + vote, + rationale, + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn update_rationale( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, + proposal_id: u64, + rationale: Option, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::UpdateRationale { + proposal_id, + rationale, + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn _execute_proposal( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + auth: Auth, + proposal_id: u64, +) { + let mut sender = Addr::unchecked(""); + match auth.clone() { + Auth::ViewingKey { address, .. } => { + sender = Addr::unchecked(address); + } + _ => (), + } + + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::Execute { auth, proposal_id }, + &[], + ) + .unwrap(); +} - id +pub(crate) fn close_proposal_should_fail( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, + proposal_id: u64, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::Close { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() } -pub(crate) fn mint_cw20s( +pub(crate) fn _close_proposal( app: &mut App, - cw20_contract: &Addr, - sender: &Addr, - receiver: &str, - amount: u128, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, + proposal_id: u64, ) { app.execute_contract( - sender.clone(), - cw20_contract.clone(), - &cw20::Cw20ExecuteMsg::Mint { - recipient: receiver.to_string(), - amount: Uint128::new(amount), + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, }, + &ExecuteMsg::Close { proposal_id }, &[], ) .unwrap(); } + +pub(crate) fn update_config( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, + query_auth: RawContract, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::UpdateConfig { + voting_strategy: dao_voting::multiple_choice::VotingStrategy::SingleChoice { + quorum: dao_voting::threshold::PercentageThreshold::Majority {}, + }, + max_voting_period: Duration::Time(604800), // One week. + min_voting_period: None, + only_members_execute: true, + allow_revoting: false, + dao: "dao_address".to_string(), + code_hash: "dao_code_hash".to_string(), + close_proposal_on_execution_failure: true, + veto: None, + query_auth, + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn update_config_should_fail( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, + query_auth: RawContract, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::UpdateConfig { + voting_strategy: dao_voting::multiple_choice::VotingStrategy::SingleChoice { + quorum: dao_voting::threshold::PercentageThreshold::Majority {}, + }, + max_voting_period: Duration::Time(604800), // One week. + min_voting_period: None, + only_members_execute: true, + allow_revoting: false, + dao: "dao_address".to_string(), + code_hash: "dao_code_hash".to_string(), + close_proposal_on_execution_failure: true, + veto: None, + query_auth, + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn update_pre_propose_info( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::UpdatePreProposeInfo { + info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {}, + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn update_pre_propose_info_should_fail( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::UpdatePreProposeInfo { + info: dao_voting::pre_propose::PreProposeInfo::AnyoneMayPropose {}, + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn execute_veto_fails( + app: &mut App, + proposal_multiple: &Addr, + proposal_multiple_code_hash: String, + sender: &str, + proposal_id: u64, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_multiple.clone(), + code_hash: proposal_multiple_code_hash, + }, + &ExecuteMsg::Veto { proposal_id }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn add_proposal_hook( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::AddProposalHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn add_proposal_hook_should_fail( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::AddProposalHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn remove_proposal_hook( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::RemoveProposalHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn remove_proposal_hook_should_fail( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::RemoveProposalHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn add_vote_hook( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::AddVoteHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn add_vote_hook_should_fail( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::AddVoteHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn remove_vote_hook( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::RemoveVoteHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap(); +} + +pub(crate) fn remove_vote_hook_should_fail( + app: &mut App, + proposal_module: &Addr, + proposal_module_code_hash: String, + sender: &str, + hook_addr: &str, + hook_code_hash: &str, +) -> ContractError { + app.execute_contract( + Addr::unchecked(sender), + &ContractInfo { + address: proposal_module.clone(), + code_hash: proposal_module_code_hash, + }, + &ExecuteMsg::RemoveVoteHook { + address: hook_addr.to_string(), + code_hash: hook_code_hash.to_string(), + }, + &[], + ) + .unwrap_err() + .downcast() + .unwrap() +} + +pub(crate) fn create_viewing_key( + app: &mut App, + contract_info: ContractInfo, + info: MessageInfo, +) -> String { + let msg = shade_protocol::contract_interfaces::query_auth::ExecuteMsg::CreateViewingKey { + entropy: "entropy".to_string(), + padding: None, + }; + let res = app + .execute_contract(info.sender, &contract_info, &msg, &[]) + .unwrap(); + let mut viewing_key = String::new(); + let data: shade_protocol::contract_interfaces::query_auth::ExecuteAnswer = + from_binary(&res.data.unwrap()).unwrap(); + if let shade_protocol::contract_interfaces::query_auth::ExecuteAnswer::CreateViewingKey { + key, + } = data + { + viewing_key = key; + }; + viewing_key +} diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs index 8fd7e0c..7a5ac15 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/instantiate.rs @@ -1,848 +1,48 @@ -use cosmwasm_std::{to_json_binary, Addr, Coin, Empty, Uint128}; -use cw20::Cw20Coin; -use cw_multi_test::{next_block, App, BankSudo, ContractWrapper, Executor, SudoMsg}; -use cw_utils::Duration; -use dao_interface::state::{Admin, ModuleInstantiateInfo}; -use dao_pre_propose_multiple as cppm; -use dao_testing::contracts::{ - cw20_balances_voting_contract, cw20_base_contract, cw20_stake_contract, - cw20_staked_balances_voting_contract, cw4_group_contract, cw721_base_contract, - dao_dao_contract, native_staked_balances_voting_contract, pre_propose_multiple_contract, -}; -use dao_voting::{ - deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, - multiple_choice::VotingStrategy, - pre_propose::PreProposeInfo, - threshold::{ActiveThreshold, ActiveThreshold::AbsoluteCount, PercentageThreshold}, -}; -use dao_voting_cw4::msg::GroupContract; - -use crate::testing::tests::ALTERNATIVE_ADDR; -use crate::{ - msg::InstantiateMsg, testing::tests::proposal_multiple_contract, testing::tests::CREATOR_ADDR, -}; - -#[allow(dead_code)] -fn get_pre_propose_info( - app: &mut App, - deposit_info: Option, - open_proposal_submission: bool, -) -> PreProposeInfo { - let pre_propose_contract = app.store_code(pre_propose_multiple_contract()); - PreProposeInfo::ModuleMayPropose { - info: ModuleInstantiateInfo { - code_id: pre_propose_contract, - msg: to_json_binary(&cppm::InstantiateMsg { - deposit_info, - open_proposal_submission, - extension: Empty::default(), - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "pre_propose_contract".to_string(), - }, - } -} - -pub fn _get_default_token_dao_proposal_module_instantiate(app: &mut App) -> InstantiateMsg { - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - +use super::{contracts::query_auth_contract, CREATOR_ADDR}; +use crate::msg::InstantiateMsg; +use cosmwasm_std::{to_binary, Addr, ContractInfo}; +use dao_voting::{pre_propose::PreProposeInfo, threshold::PercentageThreshold}; +use secret_multi_test::{App, Executor}; +use secret_utils::Duration; +use shade_protocol::utils::asset::RawContract; + +pub(crate) fn get_default_token_dao_proposal_module_instantiate( + query_auth: RawContract, + dao_code_hash: String, +) -> InstantiateMsg { InstantiateMsg { - voting_strategy, - max_voting_period: Duration::Time(604800), // One week. - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - pre_propose_info: get_pre_propose_info( - app, - Some(UncheckedDepositInfo { - denom: dao_voting::deposit::DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(10_000_000), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - close_proposal_on_execution_failure: true, veto: None, - } -} - -// Same as above but no proposal deposit. -fn _get_default_non_token_dao_proposal_module_instantiate(app: &mut App) -> InstantiateMsg { - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - - InstantiateMsg { - voting_strategy, + voting_strategy: dao_voting::multiple_choice::VotingStrategy::SingleChoice { + quorum: PercentageThreshold::Majority {}, + }, max_voting_period: Duration::Time(604800), // One week. min_voting_period: None, only_members_execute: true, allow_revoting: false, - pre_propose_info: get_pre_propose_info(app, None, false), + pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, close_proposal_on_execution_failure: true, - veto: None, - } -} - -pub fn _instantiate_with_staked_cw721_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_multiple_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let cw721_id = app.store_code(cw721_base_contract()); - let cw721_stake_id = app.store_code({ - let contract = ContractWrapper::new( - dao_voting_cw721_staked::contract::execute, - dao_voting_cw721_staked::contract::instantiate, - dao_voting_cw721_staked::contract::query, - ); - Box::new(contract) - }); - let core_contract_id = app.store_code(dao_dao_contract()); - - let nft_address = app - .instantiate_contract( - cw721_id, - Addr::unchecked("ekez"), - &cw721_base::msg::InstantiateMsg { - minter: "ekez".to_string(), - symbol: "token".to_string(), - name: "ekez token best token".to_string(), - }, - &[], - "nft-staking", - None, - ) - .unwrap(); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: cw721_stake_id, - msg: to_json_binary(&dao_voting_cw721_staked::msg::InstantiateMsg { - unstaking_duration: None, - nft_contract: dao_voting_cw721_staked::msg::NftContract::Existing { - address: nft_address.to_string(), - }, - active_threshold: None, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - dao_uri: None, - }; - - let core_addr = app - .instantiate_contract( - core_contract_id, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let staking_addr = core_state.voting_module; - - for Cw20Coin { address, amount } in initial_balances { - for i in 0..amount.u128() { - app.execute_contract( - Addr::unchecked("ekez"), - nft_address.clone(), - &cw721_base::msg::ExecuteMsg::, Empty>::Mint { - token_id: format!("{address}_{i}"), - owner: address.clone(), - token_uri: None, - extension: None, - }, - &[], - ) - .unwrap(); - app.execute_contract( - Addr::unchecked(address.clone()), - nft_address.clone(), - &cw721_base::msg::ExecuteMsg::, Empty>::SendNft { - contract: staking_addr.to_string(), - token_id: format!("{address}_{i}"), - msg: to_json_binary("").unwrap(), - }, - &[], - ) - .unwrap(); - } - } - - // Update the block so that staked balances appear. - app.update_block(|block| block.height += 1); - - core_addr -} - -pub fn instantiate_with_native_staked_balances_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_multiple_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - // Collapse balances so that we can test double votes. - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let native_stake_id = app.store_code(native_staked_balances_voting_contract()); - let core_contract_id = app.store_code(dao_dao_contract()); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: native_stake_id, - msg: to_json_binary(&dao_voting_token_staked::msg::InstantiateMsg { - token_info: dao_voting_token_staked::msg::TokenInfo::Existing { - denom: "ujuno".to_string(), - }, - unstaking_duration: None, - active_threshold: None, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - dao_uri: None, - }; - - let core_addr = app - .instantiate_contract( - core_contract_id, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let native_staking_addr = gov_state.voting_module; - - for Cw20Coin { address, amount } in initial_balances { - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: address.clone(), - amount: vec![Coin { - denom: "ujuno".to_string(), - // Double the amount so that we can stake half the balance. - amount: amount * Uint128::new(2), - }], - })) - .unwrap(); - app.execute_contract( - Addr::unchecked(&address), - native_staking_addr.clone(), - &dao_voting_token_staked::msg::ExecuteMsg::Stake {}, - &[Coin { - amount, - denom: "ujuno".to_string(), - }], - ) - .unwrap(); + dao_code_hash, + query_auth, } - - app.update_block(next_block); - - core_addr } -pub fn instantiate_with_cw20_balances_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_multiple_contract()); - - let cw20_id = app.store_code(cw20_base_contract()); - let core_id = app.store_code(dao_dao_contract()); - let votemod_id = app.store_code(cw20_balances_voting_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - // Collapse balances so that we can test double votes. - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let governance_instantiate = dao_interface::msg::InstantiateMsg { - 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: votemod_id, - msg: to_json_binary(&dao_voting_cw20_balance::msg::InstantiateMsg { - token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, - label: "DAO DAO governance token".to_string(), - name: "DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances, - marketing: None, - }, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO voting module".to_string(), +pub(crate) fn instantiate_query_auth(app: &mut App) -> ContractInfo { + let query_auth_info = app.store_code(query_auth_contract()); + let msg = shade_protocol::contract_interfaces::query_auth::InstantiateMsg { + admin_auth: shade_protocol::Contract { + address: Addr::unchecked("admin_contract"), + code_hash: "code_hash".to_string(), }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - dao_uri: None, + prng_seed: to_binary("seed").unwrap(), }; app.instantiate_contract( - core_id, + query_auth_info, Addr::unchecked(CREATOR_ADDR), - &governance_instantiate, + &msg, &[], - "DAO DAO", + "query_auth", None, ) .unwrap() } - -pub fn instantiate_with_staked_balances_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_multiple_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - // Collapse balances so that we can test double votes. - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_stake_id = app.store_code(cw20_stake_contract()); - let staked_balances_voting_id = app.store_code(cw20_staked_balances_voting_contract()); - let core_contract_id = app.store_code(dao_dao_contract()); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: staked_balances_voting_id, - msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { - active_threshold: None, - token_info: dao_voting_cw20_staked::msg::TokenInfo::New { - code_id: cw20_id, - label: "DAO DAO governance token.".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: initial_balances.clone(), - marketing: None, - staking_code_id: cw20_stake_id, - unstaking_duration: Some(Duration::Height(6)), - initial_dao_balance: None, - }, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - dao_uri: None, - }; - - let core_addr = app - .instantiate_contract( - core_contract_id, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let voting_module = gov_state.voting_module; - - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - // Stake all the initial balances. - for Cw20Coin { address, amount } in initial_balances { - app.execute_contract( - Addr::unchecked(address), - token_contract.clone(), - &cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount, - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }, - &[], - ) - .unwrap(); - } - - // Update the block so that those staked balances appear. - app.update_block(|block| block.height += 1); - - core_addr -} - -pub fn instantiate_with_multiple_staked_balances_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_multiple_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![ - Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: ALTERNATIVE_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }, - ] - }); - - // Collapse balances so that we can test double votes. - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|Cw20Coin { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_stake_id = app.store_code(cw20_stake_contract()); - let staked_balances_voting_id = app.store_code(cw20_staked_balances_voting_contract()); - let core_contract_id = app.store_code(dao_dao_contract()); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - image_url: None, - automatically_add_cw20s: true, - automatically_add_cw721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: staked_balances_voting_id, - msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { - active_threshold: Some(AbsoluteCount { - count: Uint128::one(), - }), - token_info: dao_voting_cw20_staked::msg::TokenInfo::New { - code_id: cw20_id, - label: "DAO DAO governance token.".to_string(), - name: "DAO DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: initial_balances.clone(), - marketing: None, - staking_code_id: cw20_stake_id, - unstaking_duration: Some(Duration::Height(6)), - initial_dao_balance: None, - }, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - dao_uri: None, - }; - - let core_addr = app - .instantiate_contract( - core_contract_id, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let voting_module = gov_state.voting_module; - - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - // Stake all the initial balances. - for Cw20Coin { address, amount } in initial_balances { - app.execute_contract( - Addr::unchecked(address), - token_contract.clone(), - &cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount, - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }, - &[], - ) - .unwrap(); - } - - // Update the block so that those staked balances appear. - app.update_block(|block| block.height += 1); - - core_addr -} - -pub fn instantiate_with_staking_active_threshold( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, - active_threshold: Option, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_multiple_contract()); - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_staking_id = app.store_code(cw20_stake_contract()); - let core_id = app.store_code(dao_dao_contract()); - let votemod_id = app.store_code(cw20_staked_balances_voting_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - let governance_instantiate = dao_interface::msg::InstantiateMsg { - 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: votemod_id, - msg: to_json_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { - token_info: dao_voting_cw20_staked::msg::TokenInfo::New { - code_id: cw20_id, - label: "DAO DAO governance token".to_string(), - name: "DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances, - marketing: None, - staking_code_id: cw20_staking_id, - unstaking_duration: None, - initial_dao_balance: None, - }, - active_threshold, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module".to_string(), - }], - initial_items: None, - dao_uri: None, - }; - - app.instantiate_contract( - core_id, - Addr::unchecked(CREATOR_ADDR), - &governance_instantiate, - &[], - "DAO DAO", - None, - ) - .unwrap() -} - -pub fn _instantiate_with_cw4_groups_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_weights: Option>, -) -> Addr { - let proposal_module_code_id = app.store_code(proposal_multiple_contract()); - let cw4_id = app.store_code(cw4_group_contract()); - let core_id = app.store_code(dao_dao_contract()); - let votemod_id = app.store_code(cw4_group_contract()); - - let initial_weights = initial_weights.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(1), - }] - }); - - // Remove duplicates so that we can test duplicate voting. - let initial_weights: Vec = { - let mut already_seen = vec![]; - initial_weights - .into_iter() - .filter(|Cw20Coin { address, .. }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .map(|Cw20Coin { address, amount }| cw4::Member { - addr: address, - weight: amount.u128() as u64, - }) - .collect() - }; - - let governance_instantiate = dao_interface::msg::InstantiateMsg { - 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: votemod_id, - msg: to_json_binary(&dao_voting_cw4::msg::InstantiateMsg { - group_contract: GroupContract::New { - cw4_group_code_id: cw4_id, - initial_members: initial_weights, - }, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_code_id, - msg: to_json_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module".to_string(), - }], - initial_items: None, - dao_uri: None, - }; - - let addr = app - .instantiate_contract( - core_id, - Addr::unchecked(CREATOR_ADDR), - &governance_instantiate, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - // Update the block so that weights appear. - app.update_block(|block| block.height += 1); - - addr -} diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/mod.rs b/contracts/proposal/dao-proposal-multiple/src/testing/mod.rs index 6c0a224..b994edf 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/mod.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/mod.rs @@ -1,6 +1,8 @@ -pub mod adversarial_tests; -pub mod do_votes; +pub mod contracts; pub mod execute; pub mod instantiate; pub mod queries; pub mod tests; + +pub(crate) const CREATOR_ADDR: &str = "creator"; +pub(crate) const DAO_ADDR: &str = "dao"; diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/queries.rs b/contracts/proposal/dao-proposal-multiple/src/testing/queries.rs index da52838..6ab0d1c 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/queries.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/queries.rs @@ -1,177 +1,18 @@ -use cosmwasm_std::{Addr, Uint128}; -use cw_hooks::HooksResponse; -use cw_multi_test::App; -use dao_interface::state::{ProposalModule, ProposalModuleStatus}; -use dao_pre_propose_multiple as cppm; -use dao_voting::pre_propose::ProposalCreationPolicy; +use cosmwasm_std::Addr; +use secret_multi_test::App; -use crate::{ - msg::QueryMsg, - query::{ProposalListResponse, ProposalResponse}, - state::Config, -}; +use crate::msg::QueryMsg; -pub fn query_deposit_config_and_pre_propose_module( +pub(crate) fn query_next_proposal_id( app: &App, proposal_multiple: &Addr, -) -> (cppm::Config, Addr) { - let proposal_creation_policy = query_creation_policy(app, proposal_multiple); - - if let ProposalCreationPolicy::Module { addr: module_addr } = proposal_creation_policy { - let deposit_config = query_pre_proposal_multiple_config(app, &module_addr); - - (deposit_config, module_addr) - } else { - panic!("no pre-propose module.") - } -} - -pub fn query_proposal_config(app: &App, proposal_multiple: &Addr) -> Config { - app.wrap() - .query_wasm_smart(proposal_multiple, &QueryMsg::Config {}) - .unwrap() -} - -pub fn query_creation_policy(app: &App, proposal_multiple: &Addr) -> ProposalCreationPolicy { - app.wrap() - .query_wasm_smart(proposal_multiple, &QueryMsg::ProposalCreationPolicy {}) - .unwrap() -} - -pub fn query_pre_proposal_multiple_config(app: &App, pre_propose: &Addr) -> cppm::Config { - app.wrap() - .query_wasm_smart(pre_propose, &cppm::QueryMsg::Config {}) - .unwrap() -} - -pub fn query_multiple_proposal_module(app: &App, core_addr: &Addr) -> Addr { - let modules: Vec = app - .wrap() - .query_wasm_smart( - core_addr, - &dao_interface::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - - // Filter out disabled modules. - let modules = modules - .into_iter() - .filter(|module| module.status == ProposalModuleStatus::Enabled) - .collect::>(); - - assert_eq!( - modules.len(), - 1, - "wrong proposal module count. expected 1, got {}", - modules.len() - ); - - modules.into_iter().next().unwrap().address -} - -pub fn query_list_proposals( - app: &App, - proposal_multiple: &Addr, - start_after: Option, - limit: Option, -) -> ProposalListResponse { + proposal_multiple_code_hash: String, +) -> u64 { app.wrap() .query_wasm_smart( + proposal_multiple_code_hash, proposal_multiple, - &QueryMsg::ListProposals { start_after, limit }, + &QueryMsg::NextProposalId {}, ) .unwrap() } - -pub fn query_proposal_hooks(app: &App, proposal_multiple: &Addr) -> HooksResponse { - app.wrap() - .query_wasm_smart(proposal_multiple, &QueryMsg::ProposalHooks {}) - .unwrap() -} - -pub fn query_vote_hooks(app: &App, proposal_multiple: &Addr) -> HooksResponse { - app.wrap() - .query_wasm_smart(proposal_multiple, &QueryMsg::VoteHooks {}) - .unwrap() -} - -pub fn query_list_proposals_reverse( - app: &App, - proposal_multiple: &Addr, - start_before: Option, - limit: Option, -) -> ProposalListResponse { - app.wrap() - .query_wasm_smart( - proposal_multiple, - &QueryMsg::ReverseProposals { - start_before, - limit, - }, - ) - .unwrap() -} - -pub fn query_dao_token(app: &App, core_addr: &Addr) -> Addr { - let voting_module = query_voting_module(app, core_addr); - app.wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap() -} - -pub fn query_voting_module(app: &App, core_addr: &Addr) -> Addr { - app.wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap() -} - -pub fn query_cw20_token_staking_contracts(app: &App, core_addr: &Addr) -> (Addr, Addr) { - let voting_module: Addr = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::TokenContract {}, - ) - .unwrap(); - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - (token_contract, staking_contract) -} - -pub fn query_balance_cw20, U: Into>( - app: &App, - contract_addr: T, - address: U, -) -> Uint128 { - let msg = cw20::Cw20QueryMsg::Balance { - address: address.into(), - }; - let result: cw20::BalanceResponse = app.wrap().query_wasm_smart(contract_addr, &msg).unwrap(); - result.balance -} - -pub fn query_balance_native(app: &App, who: &str, denom: &str) -> Uint128 { - let res = app.wrap().query_balance(who, denom).unwrap(); - res.amount -} - -pub fn query_proposal(app: &App, proposal_multiple: &Addr, id: u64) -> ProposalResponse { - app.wrap() - .query_wasm_smart(proposal_multiple, &QueryMsg::Proposal { proposal_id: id }) - .unwrap() -} diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs index 59d4568..ec0acde 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs @@ -1,5731 +1,471 @@ -use cosmwasm_std::{ - to_json_binary, Addr, Coin, CosmosMsg, Decimal, Empty, Timestamp, Uint128, WasmMsg, -}; -use cw20::Cw20Coin; -use cw_denom::{CheckedDenom, UncheckedDenom}; -use cw_hooks::HooksResponse; -use cw_multi_test::{next_block, App, BankSudo, Contract, ContractWrapper, Executor, SudoMsg}; -use cw_utils::Duration; -use dao_interface::state::ProposalModule; -use dao_interface::state::{Admin, ModuleInstantiateInfo}; -use dao_voting::veto::{VetoConfig, VetoError}; -use dao_voting::{ - deposit::{ - CheckedDepositInfo, DepositRefundPolicy, DepositToken, UncheckedDepositInfo, - VotingModuleTokenType, - }, - multiple_choice::{ - CheckedMultipleChoiceOption, MultipleChoiceOption, MultipleChoiceOptionType, - MultipleChoiceOptions, MultipleChoiceVote, MultipleChoiceVotes, VotingStrategy, - MAX_NUM_CHOICES, +use cosmwasm_std::{testing::mock_info, Addr, ContractInfo}; +use dao_voting::multiple_choice::MultipleChoiceVote; +use secret_multi_test::{App, Executor}; +use shade_protocol::{basic_staking::Auth, utils::asset::RawContract}; + +use crate::testing::{ + contracts::proposal_single_contract, + execute::{ + add_proposal_hook, add_proposal_hook_should_fail, add_vote_hook, add_vote_hook_should_fail, + close_proposal_should_fail, execute_proposal_should_fail, make_proposal, + remove_proposal_hook, remove_proposal_hook_should_fail, remove_vote_hook, + remove_vote_hook_should_fail, update_rationale, vote_on_proposal_should_fail, }, - pre_propose::PreProposeInfo, - status::Status, - threshold::{ActiveThreshold, PercentageThreshold, Threshold}, + instantiate::get_default_token_dao_proposal_module_instantiate, }; -use std::ops::Add; -use std::panic; -use crate::{ - msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, - proposal::MultipleChoiceProposal, - query::{ProposalListResponse, ProposalResponse, VoteInfo, VoteListResponse, VoteResponse}, - state::Config, - testing::{ - do_votes::do_test_votes_cw20_balances, - execute::make_proposal, - instantiate::{ - instantiate_with_cw20_balances_governance, - instantiate_with_native_staked_balances_governance, - instantiate_with_staked_balances_governance, instantiate_with_staking_active_threshold, - }, - queries::{ - query_balance_cw20, query_balance_native, query_cw20_token_staking_contracts, - query_dao_token, query_deposit_config_and_pre_propose_module, query_list_proposals, - query_list_proposals_reverse, query_multiple_proposal_module, query_proposal, - query_proposal_config, query_proposal_hooks, query_vote_hooks, - }, +use super::{ + execute::{ + create_viewing_key, execute_veto_fails, update_config, update_config_should_fail, + update_pre_propose_info, update_pre_propose_info_should_fail, }, - ContractError, + instantiate::instantiate_query_auth, + CREATOR_ADDR, DAO_ADDR, }; -use dao_pre_propose_multiple as cppm; - -use dao_testing::{ - contracts::{cw20_balances_voting_contract, cw20_base_contract}, - ShouldExecute, -}; - -pub const CREATOR_ADDR: &str = "creator"; -pub const ALTERNATIVE_ADDR: &str = "alternative"; - -pub struct TestMultipleChoiceVote { - /// The address casting the vote. - pub voter: String, - /// Position on the vote. - pub position: MultipleChoiceVote, - /// Voting power of the address. - pub weight: Uint128, - /// If this vote is expected to execute. - pub should_execute: ShouldExecute, -} -pub fn proposal_multiple_contract() -> Box> { - let contract = ContractWrapper::new( - crate::contract::execute, - crate::contract::instantiate, - crate::contract::query, - ) - .with_reply(crate::contract::reply); - Box::new(contract) +struct CommonTest { + app: App, + proposal_multiple_contract_info: ContractInfo, } - -pub fn pre_propose_multiple_contract() -> Box> { - let contract = ContractWrapper::new( - cppm::contract::execute, - cppm::contract::instantiate, - cppm::contract::query, +fn setup_test(sender: &str) -> CommonTest { + let mut app = App::default(); + let proposal_module_contract_info = app.store_code(proposal_single_contract()); + let query_auth = instantiate_query_auth(&mut app); + let instantiate = get_default_token_dao_proposal_module_instantiate( + RawContract { + address: query_auth.clone().address.to_string(), + code_hash: query_auth.clone().code_hash, + }, + "dao_dao_code_hash".to_string(), ); - Box::new(contract) -} + let proposal_multiple_contract_info = app + .instantiate_contract( + proposal_module_contract_info, + Addr::unchecked(sender), + &instantiate, + &[], + "proposal_single", + None, + ) + .unwrap(); -pub fn get_pre_propose_info( - app: &mut App, - deposit_info: Option, - open_proposal_submission: bool, -) -> PreProposeInfo { - let pre_propose_contract = app.store_code(pre_propose_multiple_contract()); - PreProposeInfo::ModuleMayPropose { - info: ModuleInstantiateInfo { - code_id: pre_propose_contract, - msg: to_json_binary(&cppm::InstantiateMsg { - deposit_info, - open_proposal_submission, - extension: Empty::default(), - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "pre_propose_contract".to_string(), - }, + CommonTest { + app, + proposal_multiple_contract_info, } } #[test] -fn test_propose() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let max_voting_period = Duration::Height(6); - let quorum = PercentageThreshold::Majority {}; - - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - - let instantiate = InstantiateMsg { - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy: voting_strategy.clone(), - min_voting_period: None, - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - // Check that the config has been configured correctly. - let config: Config = query_proposal_config(&app, &govmod); - let expected = Config { - max_voting_period, - only_members_execute: false, - allow_revoting: false, - dao: core_addr, - voting_strategy: voting_strategy.clone(), - min_voting_period: None, - close_proposal_on_execution_failure: true, - veto: None, - }; - assert_eq!(config, expected); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - // Create a new proposal. - make_proposal(&mut app, &govmod, CREATOR_ADDR, mc_options.clone()); - - let created: ProposalResponse = query_proposal(&app, &govmod, 1); - - let current_block = app.block_info(); - let checked_options = mc_options.into_checked().unwrap(); - let expected = MultipleChoiceProposal { - title: "title".to_string(), - description: "description".to_string(), - proposer: Addr::unchecked(CREATOR_ADDR), - start_height: current_block.height, - expiration: max_voting_period.after(¤t_block), - choices: checked_options.options, - status: Status::Open, - voting_strategy, - total_power: Uint128::new(100_000_000), - votes: MultipleChoiceVotes { - vote_weights: vec![Uint128::zero(); 3], - }, - allow_revoting: false, - min_voting_period: None, - veto: None, - }; - - assert_eq!(created.proposal, expected); - assert_eq!(created.id, 1u64); +fn test_simple_instantiate_proposal() { + let CommonTest { + app: _, + proposal_multiple_contract_info: _, + } = setup_test(DAO_ADDR); } +// this will fail currently as custom function parse_reply_get_contract_address from event is not woring +// for testing and we can't create a normal dao due to it. #[test] -fn test_propose_wrong_num_choices() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let max_voting_period = cw_utils::Duration::Height(6); - let quorum = PercentageThreshold::Majority {}; - - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy: voting_strategy.clone(), - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - // Check that the config has been configured correctly. - let config: Config = query_proposal_config(&app, &govmod); - let expected = Config { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - dao: core_addr, - voting_strategy, - veto: None, - }; - assert_eq!(config, expected); +#[should_panic( + expected = "Generic error: Querier contract error: secret_multi_test::multi::wasm::ContractData not found" +)] +fn test_create_proposal_without_voting_module_will_fail() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let options = vec![]; + let query_auth = instantiate_query_auth(&mut app); + let viewing_key_creator = + create_viewing_key(&mut app, query_auth.clone(), mock_info(CREATOR_ADDR, &[])); - // Create a proposal with less than min choices. - let mc_options = MultipleChoiceOptions { options }; - let err = app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, + // Create Proposal + make_proposal( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + Auth::ViewingKey { + key: viewing_key_creator, + address: CREATOR_ADDR.to_string(), }, - &[], + vec![], ); - assert!(err.is_err()); +} - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }; - std::convert::TryInto::try_into(MAX_NUM_CHOICES + 1).unwrap() - ]; +#[test] +fn test_vote_on_proposal_with_invalid_proposal_id_will_fail() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - // Create proposal with more than max choices. + let query_auth = instantiate_query_auth(&mut app); + let viewing_key_creator = + create_viewing_key(&mut app, query_auth.clone(), mock_info(CREATOR_ADDR, &[])); - let mc_options = MultipleChoiceOptions { options }; - // Create a new proposal. - let err = app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod, - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, + // vote on Proposal will fail + vote_on_proposal_should_fail( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + Auth::ViewingKey { + key: viewing_key_creator, + address: CREATOR_ADDR.to_string(), }, - &[], + 1, + MultipleChoiceVote { option_id: 1 }, ); - assert!(err.is_err()); } #[test] -fn test_proposal_count_initialized_to_zero() { - let mut app = App::default(); - let _proposal_id = app.store_code(proposal_multiple_contract()); - let msg = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(10)), - }, - max_voting_period: Duration::Height(10), - min_voting_period: None, - close_proposal_on_execution_failure: true, - only_members_execute: true, - allow_revoting: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - let core_addr = instantiate_with_staked_balances_governance(&mut app, msg, None); +#[should_panic(expected = "No vote exists for proposal (1) and voter (creator)")] +fn test_update_rational_fails_for_no_proposal() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = gov_state.proposal_modules; + // update rational fais for no proposal + update_rationale( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + 1, + Some("new_rational".into()), + ); +} - assert_eq!(proposal_modules.len(), 1); - let govmod = proposal_modules.into_iter().next().unwrap().address; +#[test] +fn execute_fails_on_proposal_with_invalid_proposal() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let proposal_count: u64 = app - .wrap() - .query_wasm_smart(govmod, &QueryMsg::ProposalCount {}) - .unwrap(); + let query_auth = instantiate_query_auth(&mut app); + let viewing_key_creator = + create_viewing_key(&mut app, query_auth.clone(), mock_info(CREATOR_ADDR, &[])); - assert_eq!(proposal_count, 0); + // execute on Proposal will fail + execute_proposal_should_fail( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + Auth::ViewingKey { + key: viewing_key_creator, + address: CREATOR_ADDR.to_string(), + }, + 1, + ); } #[test] -fn test_no_early_pass_with_min_duration() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let msg = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(10)), - }, - max_voting_period: Duration::Height(10), - min_voting_period: Some(Duration::Height(2)), - only_members_execute: true, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; +fn execute_veto_fails_on_proposal_with_invalid_proposal() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let core_addr = instantiate_with_staked_balances_governance( + // veto on Proposal will fail + execute_veto_fails( &mut app, - msg, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "whale".to_string(), - amount: Uint128::new(90), - }, - ]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + 1, ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = gov_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let govmod = proposal_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - app.execute_contract( - Addr::unchecked("whale"), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Whale votes which under normal curcumstances would cause the - // proposal to pass. Because there is a min duration it does not. - app.execute_contract( - Addr::unchecked("whale"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Open); - - // Let the min voting period pass. - app.update_block(|b| b.height += 2); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Passed); } #[test] -fn test_propose_with_messages() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let msg = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(10)), - }, - max_voting_period: Duration::Height(10), - min_voting_period: None, - close_proposal_on_execution_failure: true, - only_members_execute: true, - allow_revoting: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; +fn close_proposal_fails_on_proposal_with_invalid_proposal() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let core_addr = instantiate_with_staked_balances_governance( + // close on Proposal will fail + close_proposal_should_fail( &mut app, - msg, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "whale".to_string(), - amount: Uint128::new(90), - }, - ]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + 1, ); +} - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = gov_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let govmod = proposal_modules.into_iter().next().unwrap().address; - - let config_msg = ExecuteMsg::UpdateConfig { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period: cw_utils::Duration::Height(20), - only_members_execute: false, - allow_revoting: false, - dao: "dao".to_string(), - veto: None, - }; +#[test] +fn update_config_works() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let wasm_msg = WasmMsg::Execute { - contract_addr: govmod.to_string(), - msg: to_json_binary(&config_msg).unwrap(), - funds: vec![], - }; + let query_auth = instantiate_query_auth(&mut app); - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![CosmosMsg::Wasm(wasm_msg)], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), + // update config + update_config( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, + RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, }, - ]; + ); +} - let mc_options = MultipleChoiceOptions { options }; +#[test] +fn update_config_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - app.execute_contract( - Addr::unchecked("whale"), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); + let query_auth = instantiate_query_auth(&mut app); - app.execute_contract( - Addr::unchecked("whale"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, + // update config fails + update_config_should_fail( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + RawContract { + address: query_auth.address.to_string(), + code_hash: query_auth.code_hash, }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Passed); - - // Execute the proposal and messages - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Check that config was updated by proposal message - let config: Config = query_proposal_config(&app, &govmod); - assert_eq!(config.max_voting_period, Duration::Height(20)) + ); } #[test] -#[should_panic( - expected = "min_voting_period and max_voting_period must have the same units (height or time)" -)] -fn test_min_duration_units_missmatch() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let msg = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(10)), - }, - max_voting_period: Duration::Height(10), - min_voting_period: Some(Duration::Time(2)), - only_members_execute: true, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - instantiate_with_staked_balances_governance( +fn update_pre_propose_info_works() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); + + // update pre-propose + update_pre_propose_info( &mut app, - msg, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "wale".to_string(), - amount: Uint128::new(90), - }, - ]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, ); } #[test] -#[should_panic(expected = "Min voting period must be less than or equal to max voting period")] -fn test_min_duration_larger_than_proposal_duration() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let msg = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(10)), - }, - max_voting_period: Duration::Height(10), - min_voting_period: Some(Duration::Height(11)), - only_members_execute: true, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - instantiate_with_staked_balances_governance( +fn update_pre_propose_info_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); + + // update pre-propose fails + update_pre_propose_info_should_fail( &mut app, - msg, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "wale".to_string(), - amount: Uint128::new(90), - }, - ]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, ); } #[test] -fn test_min_duration_same_as_proposal_duration() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let msg = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(10)), - }, - max_voting_period: Duration::Time(10), - min_voting_period: Some(Duration::Time(10)), - only_members_execute: true, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; +fn add_proposal_hook_works() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let core_addr = instantiate_with_staked_balances_governance( + // add proposal hook + add_proposal_hook( &mut app, - msg, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "whale".to_string(), - amount: Uint128::new(90), - }, - ]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = gov_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let govmod = proposal_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - app.execute_contract( - Addr::unchecked("whale"), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Whale votes which under normal curcumstances would cause the - // proposal to pass. Because there is a min duration it does not. - app.execute_contract( - Addr::unchecked("whale"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Open); - - // someone else can vote none of the above. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 2 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Let the min voting period pass. - app.update_block(|b| b.time = b.time.plus_seconds(10)); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Passed); } -/// Instantiate the contract and use the voting module's token -/// contract as the proposal deposit token. #[test] -fn test_voting_module_token_proposal_deposit_instantiate() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); +fn add_proposal_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - veto: None, - }; - - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let token = query_dao_token(&app, &core_addr); - - let (deposit_config, _) = query_deposit_config_and_pre_propose_module(&app, &govmod); - assert_eq!( - deposit_config.deposit_info, - Some(CheckedDepositInfo { - denom: CheckedDenom::Cw20(token), - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed - }) - ) + // add proposal hook fails + add_proposal_hook_should_fail( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + "hook_addr", + "hook_code_hash", + ); } -// Instantiate the contract and use a cw20 unrealated to the voting -// module for the proposal deposit. #[test] -fn test_different_token_proposal_deposit() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let cw20_id = app.store_code(cw20_base_contract()); - let cw20_addr = app - .instantiate_contract( - cw20_id, - Addr::unchecked(CREATOR_ADDR), - &cw20_base::msg::InstantiateMsg { - name: "OAD OAD".to_string(), - symbol: "OAD".to_string(), - decimals: 6, - initial_balances: vec![], - mint: None, - marketing: None, - }, - &[], - "random-cw20", - None, - ) - .unwrap(); +fn remove_proposal_hook_works() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let quorum = PercentageThreshold::Percent(Decimal::percent(10)); - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::Token { - denom: UncheckedDenom::Cw20(cw20_addr.to_string()), - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - veto: None, - }; + // add proposal hook + add_proposal_hook( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.clone().code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", + ); - instantiate_with_staked_balances_governance(&mut app, instantiate, None); + // remove proposal hook + remove_proposal_hook( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", + ); } -/// Try to instantiate the governance module with a non-cw20 as its -/// proposal deposit token. This should error as the `TokenInfo {}` -/// query ought to fail. #[test] -#[should_panic(expected = "Error parsing into type dao_voting_cw20_balance::msg::QueryMsg")] -fn test_bad_token_proposal_deposit() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let cw20_id = app.store_code(cw20_base_contract()); - let votemod_id = app.store_code(cw20_balances_voting_contract()); - - let votemod_addr = app - .instantiate_contract( - votemod_id, - Addr::unchecked(CREATOR_ADDR), - &dao_voting_cw20_balance::msg::InstantiateMsg { - token_info: dao_voting_cw20_balance::msg::TokenInfo::New { - code_id: cw20_id, - label: "DAO DAO governance token".to_string(), - name: "DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances: vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(1), - }], - marketing: None, - }, - }, - &[], - "random-vote-module", - None, - ) - .unwrap(); - - let quorum = PercentageThreshold::Percent(Decimal::percent(10)); - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::Token { - denom: UncheckedDenom::Cw20(votemod_addr.to_string()), - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - veto: None, - }; +fn remove_proposal_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - instantiate_with_staked_balances_governance(&mut app, instantiate, None); + // remove proposal hook fails + remove_proposal_hook_should_fail( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + "hook_addr", + "hook_code_hash", + ); } #[test] -fn test_take_proposal_deposit() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let quorum = PercentageThreshold::Percent(Decimal::percent(10)); - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); +fn remove_proposal_hook_fails_for_no_proposal_hook() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - veto: None, - }; - - let core_addr = instantiate_with_cw20_balances_governance( + // remove proposal hook fails + remove_proposal_hook_should_fail( &mut app, - instantiate, - Some(vec![Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(2), - }]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, + "hook_addr", + "hook_code_hash", ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - let (deposit_config, pre_propose_module) = - query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - .. - } = deposit_config.deposit_info.unwrap() - { - app.execute_contract( - Addr::unchecked("blue"), - pre_propose_module, - &cppm::ExecuteMsg::Propose { - msg: cppm::ProposeMessage::Propose { - title: "title".to_string(), - description: "description".to_string(), - choices: mc_options.clone(), - }, - }, - &[], - ) - .unwrap_err(); - - // Allow a proposal deposit. - app.execute_contract( - Addr::unchecked("blue"), - Addr::unchecked(token), - &cw20_base::msg::ExecuteMsg::IncreaseAllowance { - spender: govmod.to_string(), - amount: Uint128::new(1), - expires: None, - }, - &[], - ) - .unwrap(); - - make_proposal(&mut app, &govmod, "blue", mc_options); - - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(1)); - } else { - panic!() - }; } #[test] -fn test_take_native_proposal_deposit() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let quorum = PercentageThreshold::Percent(Decimal::percent(10)); - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Native, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ), - veto: None, - }; +fn add_vote_hook_works() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let core_addr = instantiate_with_native_staked_balances_governance( + // add vote hook + add_vote_hook( &mut app, - instantiate, - Some(vec![Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(2), - }]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - let (deposit_config, pre_propose_module) = - query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Native(ref denom), - .. - } = deposit_config.deposit_info.unwrap() - { - app.execute_contract( - Addr::unchecked("blue"), - pre_propose_module, - &cppm::ExecuteMsg::Propose { - msg: cppm::ProposeMessage::Propose { - title: "title".to_string(), - description: "description".to_string(), - choices: mc_options.clone(), - }, - }, - &[], - ) - .unwrap_err(); - - make_proposal(&mut app, &govmod, "blue", mc_options); - - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_native(&app, "blue", denom); - assert_eq!(balance, Uint128::new(1)); - } else { - panic!() - }; } #[test] -fn test_native_proposal_deposit() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - max_voting_period, - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::Token { - denom: UncheckedDenom::Native("ujuno".to_string()), - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::Always, - }), - false, - ), - veto: None, - }; +fn add_vote_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - let core_addr = instantiate_with_staked_balances_governance( + // add vote hook fails + add_vote_hook_should_fail( &mut app, - instantiate, - Some(vec![Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(2), - }]), + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let (deposit_config, pre_propose_module) = - query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Native(ref _token), - refund_policy, - .. - } = deposit_config.deposit_info.unwrap() - { - assert_eq!(refund_policy, DepositRefundPolicy::Always); - - let mc_options = MultipleChoiceOptions { - options: vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ], - }; - - // This will fail because deposit not send - app.execute_contract( - Addr::unchecked("blue"), - pre_propose_module.clone(), - &cppm::ExecuteMsg::Propose { - msg: cppm::ProposeMessage::Propose { - title: "title".to_string(), - description: "description".to_string(), - choices: mc_options.clone(), - }, - }, - &[], - ) - .unwrap_err(); - - // Mint blue some tokens - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: "blue".to_string(), - amount: vec![Coin { - denom: "ujuno".to_string(), - amount: Uint128::new(100), - }], - })) - .unwrap(); - - // Adding deposit will work - make_proposal(&mut app, &govmod, "blue", mc_options); - - // "blue" has been refunded - let balance = query_balance_native(&app, "blue", "ujuno"); - assert_eq!(balance, Uint128::new(99)); - - // Govmod has refunded the token - let balance = query_balance_native(&app, pre_propose_module.as_str(), "ujuno"); - assert_eq!(balance, Uint128::new(1)); - - // Vote on the proposal. - let res = app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 1 }, - rationale: None, - }, - &[], - ); - assert!(res.is_ok()); - - // Execute the proposal, this should cause the deposit to be - // refunded. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // "blue" has been refunded - let balance = query_balance_native(&app, "blue", "ujuno"); - assert_eq!(balance, Uint128::new(100)); - - // Govmod has refunded the token - let balance = query_balance_native(&app, pre_propose_module.as_str(), "ujuno"); - assert_eq!(balance, Uint128::new(0)); - } else { - panic!() - }; } #[test] -fn test_deposit_return_on_execute() { - // Will create a proposal and execute it, one token will be - // deposited to create said proposal, expectation is that the - // token is then returned once the proposal is executed. - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Passed, - None, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - true, - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - // Ger deposit info - let (deposit_config, _) = query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - .. - } = deposit_config.deposit_info.unwrap() - { - // Proposal has not been executed so deposit has not been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(9)); - - // Execute the proposal, this should cause the deposit to be - // refunded. - app.execute_contract( - Addr::unchecked("blue"), - govmod, - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(10)); - } else { - panic!() - }; -} +fn remove_vote_hook_works() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); -#[test] -fn test_deposit_return_zero() { - // Test that balance does not change when deposit is zero. - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Passed, - None, - None, - true, + // add vote hook + add_vote_hook( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.clone().code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let token = query_dao_token(&app, &core_addr); - - // Execute the proposal, this should cause the deposit to be - // refunded. - app.execute_contract( - Addr::unchecked("blue"), - govmod, - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(10)); -} - -#[test] -fn test_query_list_votes() { - let (app, core_addr) = do_test_votes_cw20_balances( - vec![ - TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }, - TestMultipleChoiceVote { - voter: "note".to_string(), - position: MultipleChoiceVote { option_id: 1 }, - weight: Uint128::new(20), - should_execute: ShouldExecute::Yes, - }, - ], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Passed, - None, - None, - true, + // remove vote hook + remove_vote_hook( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let list_votes: VoteListResponse = app - .wrap() - .query_wasm_smart( - govmod, - &QueryMsg::ListVotes { - proposal_id: 1, - start_after: None, - limit: None, - }, - ) - .unwrap(); - - let expected = vec![ - VoteInfo { - voter: Addr::unchecked("blue"), - vote: MultipleChoiceVote { option_id: 0 }, - power: Uint128::new(10), - rationale: None, - }, - VoteInfo { - voter: Addr::unchecked("note"), - vote: MultipleChoiceVote { option_id: 1 }, - power: Uint128::new(20), - rationale: None, - }, - ]; - - assert_eq!(list_votes.votes, expected) } #[test] -fn test_invalid_quorum() { - // Create a proposal that will be rejected - let (_app, _core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::from_ratio(1u128, 10u128)), - }, - Status::Rejected, - None, - None, - true, +fn remove_vote_hook_fails_for_invalid_sender() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); + + // remove vote hook fails + remove_vote_hook_should_fail( + &mut app, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + CREATOR_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); } #[test] -fn test_cant_vote_executed_or_closed() { - // Create a proposal that will be rejected - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Rejected, - None, - None, - true, - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - // Close the proposal - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Try to vote, should error - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap_err(); - - // Create a proposal that will pass - let (mut app, _core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Passed, - None, - None, - true, - ); - - // Execute the proposal - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Try to vote, should error - app.execute_contract( - Addr::unchecked("blue"), - govmod, - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap_err(); -} - -#[test] -fn test_cant_propose_zero_power() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let quorum = PercentageThreshold::Percent(Decimal::percent(10)); - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::Always, - }), - false, - ), - veto: None, - }; - - let core_addr = instantiate_with_cw20_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(1), - }, - Cw20Coin { - address: "blue2".to_string(), - amount: Uint128::new(10), - }, - ]), - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = gov_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let govmod = proposal_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - let (deposit_config, pre_propose_module) = - query_deposit_config_and_pre_propose_module(&app, &govmod); - if let Some(CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - amount, - .. - }) = deposit_config.deposit_info - { - app.execute_contract( - Addr::unchecked("blue"), - token.clone(), - &cw20_base::msg::ExecuteMsg::IncreaseAllowance { - spender: pre_propose_module.to_string(), - amount, - expires: None, - }, - &[], - ) - .unwrap(); - } - - // Blue proposes - app.execute_contract( - Addr::unchecked("blue"), - pre_propose_module.clone(), - &cppm::ExecuteMsg::Propose { - msg: cppm::ProposeMessage::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options.clone(), - }, - }, - &[], - ) - .unwrap(); - - // Should fail as blue's balance is now 0 - let err = app.execute_contract( - Addr::unchecked("blue"), - pre_propose_module, - &cppm::ExecuteMsg::Propose { - msg: cppm::ProposeMessage::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - }, - }, - &[], - ); - - assert!(err.is_err()) -} - -#[test] -fn test_cant_vote_not_registered() { - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Open, - Some(Uint128::new(100)), - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::Always, - }), - false, - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - // Should error as blue2 is not registered to vote - let err = app - .execute_contract( - Addr::unchecked("blue2"), - govmod, - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap_err(); - - assert!(matches!( - err.downcast().unwrap(), - ContractError::NotRegistered {} - )) -} - -#[test] -fn test_cant_execute_not_member() { - // Create proposal with only_members_execute: true - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let max_voting_period = cw_utils::Duration::Height(6); - let quorum = PercentageThreshold::Majority {}; - - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: true, - allow_revoting: false, - voting_strategy, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - // Create proposal - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Proposal should pass after this vote - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Execute should error as blue2 is not a member - let err = app - .execute_contract( - Addr::unchecked("blue2"), - govmod, - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - assert!(matches!( - err.downcast().unwrap(), - ContractError::Unauthorized {} - )) -} - -#[test] -fn test_cant_execute_not_member_when_proposal_created() { - // Create proposal with only_members_execute: true and ensure member cannot - // execute if they were not a member when the proposal was created - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let max_voting_period = cw_utils::Duration::Height(6); - let quorum = PercentageThreshold::Majority {}; - - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: true, - allow_revoting: false, - voting_strategy, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - // Create proposal - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Proposal should pass after this vote - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let (token_contract, staking_contract) = query_cw20_token_staking_contracts(&app, &core_addr); - // Mint funds for blue2 - app.execute_contract( - core_addr, - token_contract.clone(), - &cw20::Cw20ExecuteMsg::Mint { - recipient: "blue2".to_string(), - amount: Uint128::new(10), - }, - &[], - ) - .unwrap(); - // Have blue2 stake funds - app.execute_contract( - Addr::unchecked("blue2"), - token_contract, - &cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(10), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }, - &[], - ) - .unwrap(); - - // Update the block so that the staked balance appears. - app.update_block(|block| block.height += 1); - - // Execute should error as blue2 was not a member when the proposal was - // created even though they are now - let err = app - .execute_contract( - Addr::unchecked("blue2"), - govmod, - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - assert!(matches!( - err.downcast().unwrap(), - ContractError::Unauthorized {} - )) -} - -#[test] -fn test_open_proposal_submission() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let max_voting_period = cw_utils::Duration::Height(6); - - // Instantiate with open_proposal_submission enabled - let instantiate = InstantiateMsg { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - max_voting_period, - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: get_pre_propose_info(&mut app, None, true), - veto: None, - }; - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - make_proposal( - &mut app, - &govmod, - "random", - MultipleChoiceOptions { - options: vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ], - }, - ); - - let created: ProposalResponse = query_proposal(&app, &govmod, 1); - let current_block = app.block_info(); - let expected = MultipleChoiceProposal { - title: "title".to_string(), - description: "description".to_string(), - proposer: Addr::unchecked("random"), - start_height: current_block.height, - expiration: max_voting_period.after(¤t_block), - min_voting_period: None, - allow_revoting: false, - total_power: Uint128::new(100_000_000), - status: Status::Open, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Percent(Decimal::percent(100)), - }, - choices: vec![ - CheckedMultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - option_type: MultipleChoiceOptionType::Standard, - vote_count: Uint128::zero(), - index: 0, - title: "title".to_string(), - }, - CheckedMultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - option_type: MultipleChoiceOptionType::Standard, - vote_count: Uint128::zero(), - index: 1, - title: "title".to_string(), - }, - CheckedMultipleChoiceOption { - description: "None of the above".to_string(), - msgs: vec![], - option_type: MultipleChoiceOptionType::None, - vote_count: Uint128::zero(), - index: 2, - title: "None of the above".to_string(), - }, - ], - votes: MultipleChoiceVotes { - vote_weights: vec![Uint128::zero(); 3], - }, - veto: None, - }; - - assert_eq!(created.proposal, expected); - assert_eq!(created.id, 1u64); -} - -#[test] -fn test_close_open_proposal() { - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Open, - Some(Uint128::new(100)), - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::Always, - }), - false, - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - // Close the proposal, this should error as the proposal is still - // open and not expired. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - // Make the proposal expire. - app.update_block(|block| block.height += 10); - - // Close the proposal, this should work as the proposal is now - // open and expired. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let (deposit_config, _) = query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - .. - } = deposit_config.deposit_info.unwrap() - { - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(10)); - } else { - panic!() - }; -} - -#[test] -fn test_no_refund_failed_proposal() { - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Open, - Some(Uint128::new(100)), - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - // Make the proposal expire. - app.update_block(|block| block.height += 10); - - // Close the proposal, this should work as the proposal is now - // open and expired. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let (deposit_config, _) = query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - .. - } = deposit_config.deposit_info.unwrap() - { - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(9)); - } else { - panic!() - }; -} - -#[test] -fn test_zero_deposit() { - do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Passed, - None, - None, - true, - ); -} - -#[test] -fn test_deposit_return_on_close() { - let quorum = PercentageThreshold::Percent(Decimal::percent(10)); - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - voting_strategy, - Status::Rejected, - None, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::Always, - }), - false, - ); - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let (deposit_config, _) = query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - .. - } = deposit_config.deposit_info.unwrap() - { - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(9)); - - // Close the proposal, this should cause the deposit to be - // refunded. - app.execute_contract( - Addr::unchecked("blue"), - govmod, - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(10)); - } else { - panic!() - }; -} - -#[test] -fn test_execute_expired_proposal() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let quorum = PercentageThreshold::Percent(Decimal::percent(10)); - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(10), - }, - Cw20Coin { - address: "inactive".to_string(), - amount: Uint128::new(90), - }, - ]), - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let proposal_modules = gov_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let govmod = proposal_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Proposal has now reached quorum but should not be passed. - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Open); - - // Expire the proposal. It should now be passed as quorum was reached. - app.update_block(|b| b.height += 10); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Passed); - - // Try to close the proposal. This should fail as the proposal is - // passed. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - // Check that we can execute the proposal despite the fact that it - // is technically expired. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Can't execute more than once. - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap_err(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Executed); -} - -#[test] -fn test_update_config() { - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 0 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Passed, - None, - None, - false, - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let govmod_config: Config = query_proposal_config(&app, &govmod); - - assert_eq!( - govmod_config.voting_strategy, - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {} - } - ); - - let dao = govmod_config.dao; - - // Attempt to update the config from a non-dao address. This - // should fail as it is unauthorized. - app.execute_contract( - Addr::unchecked("wrong"), - govmod.clone(), - &ExecuteMsg::UpdateConfig { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period: cw_utils::Duration::Height(10), - only_members_execute: false, - allow_revoting: false, - dao: dao.to_string(), - veto: None, - }, - &[], - ) - .unwrap_err(); - - // Update the config from the DAO address. This should succeed. - app.execute_contract( - dao.clone(), - govmod.clone(), - &ExecuteMsg::UpdateConfig { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period: cw_utils::Duration::Height(10), - only_members_execute: false, - allow_revoting: false, - dao: Addr::unchecked(CREATOR_ADDR).to_string(), - veto: None, - }, - &[], - ) - .unwrap(); - - let govmod_config: Config = query_proposal_config(&app, &govmod); - - let expected = Config { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period: cw_utils::Duration::Height(10), - only_members_execute: false, - allow_revoting: false, - dao: Addr::unchecked(CREATOR_ADDR), - veto: None, - }; - assert_eq!(govmod_config, expected); - - // As we have changed the DAO address updating the config using - // the original one should now fail. - app.execute_contract( - dao, - govmod, - &ExecuteMsg::UpdateConfig { - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period: cw_utils::Duration::Height(10), - only_members_execute: false, - allow_revoting: false, - dao: Addr::unchecked(CREATOR_ADDR).to_string(), - veto: None, - }, - &[], - ) - .unwrap_err(); -} - -#[test] -fn test_no_return_if_no_refunds() { - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Rejected, - None, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - true, - ); - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let (deposit_config, _) = query_deposit_config_and_pre_propose_module(&app, &govmod); - if let CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - .. - } = deposit_config.deposit_info.unwrap() - { - // Close the proposal, this should cause the deposit to be - // refunded. - app.execute_contract( - Addr::unchecked("blue"), - govmod, - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap(); - - // Proposal has been executed so deposit has been refunded. - let balance = query_balance_cw20(&app, token, "blue".to_string()); - assert_eq!(balance, Uint128::new(9)); - } else { - panic!() - }; -} - -#[test] -fn test_query_list_proposals() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy: voting_strategy.clone(), - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - let gov_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100), - }]), - ); - - let gov_modules: Vec = app - .wrap() - .query_wasm_smart( - gov_addr, - &dao_interface::msg::QueryMsg::ProposalModules { - start_after: None, - limit: None, - }, - ) - .unwrap(); - assert_eq!(gov_modules.len(), 1); - - let govmod = gov_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - for _i in 1..10 { - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap(); - } - - let proposals_forward: ProposalListResponse = query_list_proposals(&app, &govmod, None, None); - let mut proposals_backward: ProposalListResponse = - query_list_proposals_reverse(&app, &govmod, None, None); - - proposals_backward.proposals.reverse(); - - assert_eq!(proposals_forward.proposals, proposals_backward.proposals); - let checked_options = mc_options.into_checked().unwrap(); - let current_block = app.block_info(); - let expected = ProposalResponse { - id: 1, - proposal: MultipleChoiceProposal { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - proposer: Addr::unchecked(CREATOR_ADDR), - start_height: current_block.height, - expiration: max_voting_period.after(¤t_block), - choices: checked_options.options.clone(), - status: Status::Open, - voting_strategy: voting_strategy.clone(), - total_power: Uint128::new(100), - votes: MultipleChoiceVotes { - vote_weights: vec![Uint128::zero(); 3], - }, - allow_revoting: false, - min_voting_period: None, - veto: None, - }, - }; - assert_eq!(proposals_forward.proposals[0], expected); - - // Get proposals (3, 5] - let proposals_forward: ProposalListResponse = - query_list_proposals(&app, &govmod, Some(3), Some(2)); - - let mut proposals_backward: ProposalListResponse = - query_list_proposals_reverse(&app, &govmod, Some(6), Some(2)); - - let expected = ProposalResponse { - id: 4, - proposal: MultipleChoiceProposal { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - proposer: Addr::unchecked(CREATOR_ADDR), - start_height: current_block.height, - expiration: max_voting_period.after(¤t_block), - choices: checked_options.options, - status: Status::Open, - voting_strategy, - total_power: Uint128::new(100), - votes: MultipleChoiceVotes { - vote_weights: vec![Uint128::zero(); 3], - }, - allow_revoting: false, - min_voting_period: None, - veto: None, - }, - }; - assert_eq!(proposals_forward.proposals[0], expected); - assert_eq!(proposals_backward.proposals[1], expected); - - proposals_backward.proposals.reverse(); - assert_eq!(proposals_forward.proposals, proposals_backward.proposals); -} - -#[test] -fn test_hooks() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let govmod_config: Config = query_proposal_config(&app, &govmod); - let dao = govmod_config.dao; - - // Expect no hooks - let hooks: HooksResponse = query_proposal_hooks(&app, &govmod); - assert_eq!(hooks.hooks.len(), 0); - - let hooks: HooksResponse = query_vote_hooks(&app, &govmod); - assert_eq!(hooks.hooks.len(), 0); - - let msg = ExecuteMsg::AddProposalHook { - address: "some_addr".to_string(), - }; - - // Expect error as sender is not DAO - let _err = app - .execute_contract(Addr::unchecked(CREATOR_ADDR), govmod.clone(), &msg, &[]) - .unwrap_err(); - - // Expect success as sender is now DAO - let _res = app - .execute_contract(dao.clone(), govmod.clone(), &msg, &[]) - .unwrap(); - - let hooks: HooksResponse = query_proposal_hooks(&app, &govmod); - assert_eq!(hooks.hooks.len(), 1); - - // Expect error as hook is already set - let _err = app - .execute_contract(dao.clone(), govmod.clone(), &msg, &[]) - .unwrap_err(); - - // Expect error as hook does not exist - let _err = app - .execute_contract( - dao.clone(), - govmod.clone(), - &ExecuteMsg::RemoveProposalHook { - address: "not_exist".to_string(), - }, - &[], - ) - .unwrap_err(); - - let msg = ExecuteMsg::RemoveProposalHook { - address: "some_addr".to_string(), - }; - - // Expect error as sender is not DAO - let _err = app - .execute_contract(Addr::unchecked(CREATOR_ADDR), govmod.clone(), &msg, &[]) - .unwrap_err(); - - // Expect success - let _res = app - .execute_contract(dao.clone(), govmod.clone(), &msg, &[]) - .unwrap(); - - let msg = ExecuteMsg::AddVoteHook { - address: "some_addr".to_string(), - }; - - // Expect error as sender is not DAO - let _err = app - .execute_contract(Addr::unchecked(CREATOR_ADDR), govmod.clone(), &msg, &[]) - .unwrap_err(); - - // Expect success as sender is now DAO - let _res = app - .execute_contract(dao.clone(), govmod.clone(), &msg, &[]) - .unwrap(); - - let hooks: HooksResponse = query_vote_hooks(&app, &govmod); - assert_eq!(hooks.hooks.len(), 1); - - // Expect error as hook is already set - let _err = app - .execute_contract(dao.clone(), govmod.clone(), &msg, &[]) - .unwrap_err(); - - // Expect error as hook does not exist - let _err = app - .execute_contract( - dao.clone(), - govmod.clone(), - &ExecuteMsg::RemoveVoteHook { - address: "not_exist".to_string(), - }, - &[], - ) - .unwrap_err(); - - let msg = ExecuteMsg::RemoveVoteHook { - address: "some_addr".to_string(), - }; - - // Expect error as sender is not DAO - let _err = app - .execute_contract(Addr::unchecked(CREATOR_ADDR), govmod.clone(), &msg, &[]) - .unwrap_err(); - - // Expect success - let _res = app.execute_contract(dao, govmod, &msg, &[]).unwrap(); -} - -#[test] -fn test_active_threshold_absolute() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staking_active_threshold( - &mut app, - instantiate, - None, - Some(ActiveThreshold::AbsoluteCount { - count: Uint128::new(100), - }), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let govmod_config: Config = query_proposal_config(&app, &govmod); - let dao = govmod_config.dao; - let voting_module: Addr = app - .wrap() - .query_wasm_smart(dao, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap(); - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - // Try and create a proposal, will fail as inactive - let _err = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &crate::msg::ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap_err(); - - // Stake enough tokens - let msg = cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(100), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), token_contract, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Try and create a proposal, will now succeed as enough tokens are staked - let _res = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &crate::msg::ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap(); - - // Unstake some tokens to make it inactive again - let msg = cw20_stake::msg::ExecuteMsg::Unstake { - amount: Uint128::new(50), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), staking_contract, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Try and create a proposal, will fail as no longer active - let _err = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod, - &crate::msg::ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "This is a simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap_err(); -} - -#[test] -fn test_active_threshold_percent() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - // 20% needed to be active, 20% of 100000000 is 20000000 - let core_addr = instantiate_with_staking_active_threshold( - &mut app, - instantiate, - None, - Some(ActiveThreshold::Percentage { - percent: Decimal::percent(20), - }), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let govmod_config: Config = query_proposal_config(&app, &govmod); - let dao = govmod_config.dao; - let voting_module: Addr = app - .wrap() - .query_wasm_smart(dao, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap(); - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - // Try and create a proposal, will fail as inactive - let _res = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap_err(); - - // Stake enough tokens - let msg = cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(20000000), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), token_contract, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Try and create a proposal, will now succeed as enough tokens are staked - let _res = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap(); - - // Unstake some tokens to make it inactive again - let msg = cw20_stake::msg::ExecuteMsg::Unstake { - amount: Uint128::new(1000), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), staking_contract, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - // Try and create a proposal, will fail as no longer active - let _res = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod, - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap_err(); -} - -#[test] -fn test_active_threshold_none() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - min_voting_period: None, - close_proposal_on_execution_failure: true, - max_voting_period, - only_members_execute: false, - allow_revoting: false, - voting_strategy, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = - instantiate_with_staking_active_threshold(&mut app, instantiate.clone(), None, None); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let govmod_config: Config = query_proposal_config(&app, &govmod); - let dao = govmod_config.dao; - let voting_module: Addr = app - .wrap() - .query_wasm_smart(dao, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap(); - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - // Stake some tokens so we can propose - let msg = cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(2000), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }; - app.execute_contract(Addr::unchecked(CREATOR_ADDR), token_contract, &msg, &[]) - .unwrap(); - app.update_block(next_block); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - // Try and create a proposal, will succeed as no threshold - let _res = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod, - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap(); - - // Now try with balance voting to test when IsActive is not implemented - // on the contract - let _threshold = Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Majority {}, - }; - let _max_voting_period = cw_utils::Duration::Height(6); - - let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - // Try and create a proposal, will succeed as IsActive is not implemented - let _res = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod, - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); -} - -/// Basic test for revoting on prop-multiple -#[test] -fn test_revoting() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: true, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // a-1 votes, vote_weights: [100_000_000, 0] - app.execute_contract( - Addr::unchecked("a-1"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // a-2 votes, vote_weights: [100_000_000, 100_000_000] - app.execute_contract( - Addr::unchecked("a-2"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 1 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Time passes.. - app.update_block(|b| b.height += 2); - - // Assert that both vote options have equal vote weights at some block - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Open); - assert_eq!( - proposal.proposal.votes.vote_weights[0], - Uint128::new(100_000_000), - ); - assert_eq!( - proposal.proposal.votes.vote_weights[1], - Uint128::new(100_000_000), - ); - - // More time passes.. - app.update_block(|b| b.height += 3); - - // Last moment a-2 has a change of mind, - // votes shift to [200_000_000, 0] - app.execute_contract( - Addr::unchecked("a-2"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - app.update_block(next_block); - - // Assert that revote succeeded - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Passed); - assert_eq!( - proposal.proposal.votes.vote_weights[0], - Uint128::new(200_000_000), - ); - assert_eq!(proposal.proposal.votes.vote_weights[1], Uint128::new(0),); -} - -/// Tests that revoting is stored at a per-proposal level. -/// Proposals created while revoting is enabled should not -/// have it disabled if a config change turns if off. -#[test] -fn test_allow_revoting_config_changes() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: true, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options that allows revoting - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap(); - - // Disable revoting - app.execute_contract( - core_addr.clone(), - proposal_module.clone(), - &ExecuteMsg::UpdateConfig { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - dao: core_addr.to_string(), - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - veto: None, - }, - &[], - ) - .unwrap(); - - // Assert that proposal_id: 1 still allows revoting - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); - assert!(proposal.proposal.allow_revoting); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 1 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // New proposals should not allow revoting - app.execute_contract( - Addr::unchecked("a-2"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A very complex text proposal".to_string(), - description: "A very complex text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-2"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 2, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("a-2"), - proposal_module, - &ExecuteMsg::Vote { - proposal_id: 2, - vote: MultipleChoiceVote { option_id: 1 }, - rationale: None, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert!(matches!(err, ContractError::AlreadyVoted {})); -} - -/// Tests that we error if a revote casts the same vote as the -/// previous vote. -#[test] -fn test_revoting_same_vote_twice() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: true, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let proprosal_module = query_multiple_proposal_module(&app, &core_addr); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options that allows revoting - app.execute_contract( - Addr::unchecked("a-1"), - proprosal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Cast a vote - app.execute_contract( - Addr::unchecked("a-1"), - proprosal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Revote for the same option as currently voted - let err: ContractError = app - .execute_contract( - Addr::unchecked("a-1"), - proprosal_module, - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - // Can't cast the same vote twice. - assert!(matches!(err, ContractError::AlreadyCast {})); -} - -/// Tests that revoting into a non-existing vote option -/// does not invalidate the initial vote -#[test] -fn test_invalid_revote_does_not_invalidate_initial_vote() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: true, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // a-1 votes, vote_weights: [100_000_000, 0] - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // a-2 votes, vote_weights: [100_000_000, 100_000_000] - app.execute_contract( - Addr::unchecked("a-2"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 1 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - app.update_block(next_block); - - // Assert that both vote options have equal vote weights at some block - let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); - assert_eq!(proposal.proposal.status, Status::Open); - assert_eq!( - proposal.proposal.votes.vote_weights[0], - Uint128::new(100_000_000), - ); - assert_eq!( - proposal.proposal.votes.vote_weights[1], - Uint128::new(100_000_000), - ); - - // Time passes.. - app.update_block(|b| b.height += 3); - - // Last moment a-2 has a change of mind and attempts - // to vote for a non-existing option - let err: ContractError = app - .execute_contract( - Addr::unchecked("a-2"), - proposal_module, - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 99 }, - rationale: None, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - // Assert that prior votes remained the same - assert_eq!( - proposal.proposal.votes.vote_weights[0], - Uint128::new(100_000_000), - ); - assert_eq!( - proposal.proposal.votes.vote_weights[1], - Uint128::new(100_000_000), - ); - assert!(matches!(err, ContractError::InvalidVote {})); -} - -#[test] -fn test_return_deposit_to_dao_on_proposal_failure() { - let (mut app, core_addr) = do_test_votes_cw20_balances( - vec![TestMultipleChoiceVote { - voter: "blue".to_string(), - position: MultipleChoiceVote { option_id: 2 }, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], - VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - Status::Open, - Some(Uint128::new(100)), - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - refund_policy: DepositRefundPolicy::OnlyPassed, - }), - false, - ); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_addr.clone(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let proposal_modules = core_state.proposal_modules; - - assert_eq!(proposal_modules.len(), 1); - let proposal_multiple = proposal_modules.into_iter().next().unwrap().address; - - // Make the proposal expire. It has now failed. - app.update_block(|block| block.height += 10); - - // Close the proposal, this should work as the proposal is now - // open and expired. - app.execute_contract( - Addr::unchecked("keze"), - proposal_multiple.clone(), - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let (deposit_config, _) = query_deposit_config_and_pre_propose_module(&app, &proposal_multiple); - if let CheckedDepositInfo { - denom: CheckedDenom::Cw20(ref token), - .. - } = deposit_config.deposit_info.unwrap() - { - // // Deposit should now belong to the DAO. - let balance = query_balance_cw20(&app, token, core_addr.to_string()); - assert_eq!(balance, Uint128::new(1)); - } else { - panic!() - }; -} - -#[test] -fn test_close_failed_proposal() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - - let quorum = PercentageThreshold::Majority {}; - let voting_strategy = VotingStrategy::SingleChoice { quorum }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - max_voting_period, - voting_strategy, - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - - let core_addr = instantiate_with_staking_active_threshold(&mut app, instantiate, None, None); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let govmod_config: Config = query_proposal_config(&app, &govmod); - let dao = govmod_config.dao; - let voting_module: Addr = app - .wrap() - .query_wasm_smart(dao, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap(); - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - // Stake some tokens so we can propose - let msg = cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(2000), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - token_contract.clone(), - &msg, - &[], - ) - .unwrap(); - app.update_block(next_block); - - let msg = cw20::Cw20ExecuteMsg::Burn { - amount: Uint128::new(2000), - }; - let binary_msg = to_json_binary(&msg).unwrap(); - - let options = vec![ - MultipleChoiceOption { - description: "Burn or burn".to_string(), - msgs: vec![WasmMsg::Execute { - contract_addr: token_contract.to_string(), - msg: binary_msg, - funds: vec![], - } - .into()], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "Don't burn".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - - // Overburn tokens - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple burn tokens proposal".to_string(), - description: "Burning more tokens, than dao treasury have".to_string(), - choices: mc_options.clone(), - proposer: None, - }, - &[], - ) - .unwrap(); - - // Vote on proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Update block - let timestamp = Timestamp::from_seconds(300_000_000); - app.update_block(|block| block.time = timestamp); - - // Execute proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let failed: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(failed.proposal.status, Status::ExecutionFailed); - // With disabled feature - // Disable feature first - { - let original: Config = query_proposal_config(&app, &govmod); - - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "Disable closing failed proposals".to_string(), - description: "We want to re-execute failed proposals".to_string(), - choices: MultipleChoiceOptions { - options: vec![ - MultipleChoiceOption { - description: "Disable closing failed proposals".to_string(), - msgs: vec![WasmMsg::Execute { - contract_addr: govmod.to_string(), - msg: to_json_binary(&ExecuteMsg::UpdateConfig { - voting_strategy: VotingStrategy::SingleChoice { quorum }, - max_voting_period: original.max_voting_period, - min_voting_period: original.min_voting_period, - only_members_execute: original.only_members_execute, - allow_revoting: false, - dao: original.dao.to_string(), - close_proposal_on_execution_failure: false, - veto: None, - }) - .unwrap(), - funds: vec![], - } - .into()], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "Don't disable".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ], - }, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Vote on proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 2, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Execute proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 2 }, - &[], - ) - .unwrap(); - } - - // Overburn tokens (again), this time without reverting - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A simple burn tokens proposal".to_string(), - description: "Burning more tokens, than dao treasury have".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Vote on proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 3, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Execute proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 3 }, - &[], - ) - .expect_err("Should be sub overflow"); - - // Status should still be passed - let updated: ProposalResponse = query_proposal(&app, &govmod, 3); - - // not reverted - assert_eq!(updated.proposal.status, Status::Passed); -} - -#[test] -fn test_no_double_refund_on_execute_fail_and_close() { - let mut app = App::default(); - let _proposal_module_id = app.store_code(proposal_multiple_contract()); - - let voting_strategy = VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }; - let max_voting_period = cw_utils::Duration::Height(6); - let instantiate = InstantiateMsg { - voting_strategy, - max_voting_period, - min_voting_period: None, - only_members_execute: false, - allow_revoting: false, - close_proposal_on_execution_failure: true, - pre_propose_info: get_pre_propose_info( - &mut app, - Some(UncheckedDepositInfo { - denom: DepositToken::VotingModuleToken { - token_type: VotingModuleTokenType::Cw20, - }, - amount: Uint128::new(1), - // Important to set to true here as we want to be sure - // that we don't get a second refund on close. Refunds on - // close only happen if this is true. - refund_policy: DepositRefundPolicy::Always, - }), - false, - ), - veto: None, - }; - - let core_addr = instantiate_with_staking_active_threshold( - &mut app, - instantiate, - Some(vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - // One token for sending to the DAO treasury, one token - // for staking, one token for paying the proposal deposit. - amount: Uint128::new(3), - }]), - None, - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_config: Config = query_proposal_config(&app, &govmod); - let dao = proposal_config.dao; - let voting_module: Addr = app - .wrap() - .query_wasm_smart(dao, &dao_interface::msg::QueryMsg::VotingModule {}) - .unwrap(); - let staking_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module.clone(), - &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: Addr = app - .wrap() - .query_wasm_smart( - voting_module, - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - // Stake a token so we can propose. - let msg = cw20::Cw20ExecuteMsg::Send { - contract: staking_contract.to_string(), - amount: Uint128::new(1), - msg: to_json_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), - }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - token_contract.clone(), - &msg, - &[], - ) - .unwrap(); - app.update_block(next_block); - - // Send some tokens to the proposal module so it has the ability - // to double refund if the code is buggy. - let msg = cw20::Cw20ExecuteMsg::Transfer { - recipient: govmod.to_string(), - amount: Uint128::new(1), - }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - token_contract.clone(), - &msg, - &[], - ) - .unwrap(); - - let msg = cw20::Cw20ExecuteMsg::Burn { - amount: Uint128::new(2000), - }; - let binary_msg = to_json_binary(&msg).unwrap(); - - // Increase allowance to pay the proposal deposit. - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - token_contract.clone(), - &cw20_base::msg::ExecuteMsg::IncreaseAllowance { - spender: govmod.to_string(), - amount: Uint128::new(1), - expires: None, - }, - &[], - ) - .unwrap(); - - let choices = MultipleChoiceOptions { - options: vec![ - MultipleChoiceOption { - description: "Burning more tokens, than dao treasury have".to_string(), - msgs: vec![WasmMsg::Execute { - contract_addr: token_contract.to_string(), - msg: binary_msg, - funds: vec![], - } - .into()], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "hi there".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ], - }; - - make_proposal( - &mut app, - &govmod, - Addr::unchecked(CREATOR_ADDR).as_str(), - choices, - ); - - // Vote on proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Update block - let timestamp = Timestamp::from_seconds(300_000_000); - app.update_block(|block| block.time = timestamp); - - // Execute proposal - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let failed: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(failed.proposal.status, Status::ExecutionFailed); - - // Check that our deposit has been refunded. - let balance = query_balance_cw20(&app, token_contract.to_string(), CREATOR_ADDR); - assert_eq!(balance, Uint128::new(1)); - - // Close the proposal - this should fail as it was executed. - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod, - &ExecuteMsg::Close { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert!(matches!(err, ContractError::WrongCloseStatus {})); - - // Check that our deposit was not refunded a second time on close. - let balance = query_balance_cw20(&app, token_contract.to_string(), CREATOR_ADDR); - assert_eq!(balance, Uint128::new(1)); -} - -// Casting votes is only allowed within the proposal expiration timeframe -#[test] -pub fn test_not_allow_voting_on_expired_proposal() { - let mut app = App::default(); - let _govmod_id = app.store_code(proposal_multiple_contract()); - let instantiate = InstantiateMsg { - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - min_voting_period: None, - close_proposal_on_execution_failure: true, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }; - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - instantiate, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // assert proposal is open - let proposal = query_proposal(&app, &proposal_module, 1); - assert_eq!(proposal.proposal.status, Status::Open); - - // expire the proposal and attempt to vote - app.update_block(|block| block.height += 6); - - let err: ContractError = app - .execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod, - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - // assert the vote got rejected and did not count towards the votes - let proposal = query_proposal(&app, &proposal_module, 1); - assert_eq!(proposal.proposal.status, Status::Rejected); - assert_eq!(proposal.proposal.votes.vote_weights[0], Uint128::zero()); - assert!(matches!(err, ContractError::Expired { id: _proposal_id })); -} - -// tests the next proposal id query. -#[test] -fn test_next_proposal_id() { - let mut app = App::default(); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: true, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 2); -} - -#[test] -fn test_vote_with_rationale() { - let mut app = App::default(); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "elub".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title 1".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title 2".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A proposal".to_string(), - description: "A simple proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: Some("I think this is a good idea".to_string()), - }, - &[], - ) - .unwrap(); - - // Query rationale - let vote_resp: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod, - &QueryMsg::GetVote { - proposal_id: 1, - voter: "blue".to_string(), - }, - ) - .unwrap(); - - let vote = vote_resp.vote.unwrap(); - assert_eq!(vote.vote.option_id, 0); - assert_eq!( - vote.rationale, - Some("I think this is a good idea".to_string()) - ); -} - -#[test] -fn test_revote_with_rationale() { - let mut app = App::default(); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: true, // Enable revoting - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "elub".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title 1".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title 2".to_string(), - }, - ]; - - let mc_options = MultipleChoiceOptions { options }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A proposal".to_string(), - description: "A simple proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: Some("I think this is a good idea".to_string()), - }, - &[], - ) - .unwrap(); - - // Query rationale - let vote_resp: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod.clone(), - &QueryMsg::GetVote { - proposal_id: 1, - voter: "blue".to_string(), - }, - ) - .unwrap(); - - let vote = vote_resp.vote.unwrap(); - assert_eq!(vote.vote.option_id, 0); - assert_eq!( - vote.rationale, - Some("I think this is a good idea".to_string()) - ); - - // Revote with rationale - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 1 }, - rationale: Some("Nah".to_string()), - }, - &[], - ) - .unwrap(); - - // Query rationale and ensure it changed - let vote_resp: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod.clone(), - &QueryMsg::GetVote { - proposal_id: 1, - voter: "blue".to_string(), - }, - ) - .unwrap(); - - let vote = vote_resp.vote.unwrap(); - assert_eq!(vote.vote.option_id, 1); - assert_eq!(vote.rationale, Some("Nah".to_string())); - - // Revote without rationale - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 2 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // Query rationale and ensure it changed - let vote_resp: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod, - &QueryMsg::GetVote { - proposal_id: 1, - voter: "blue".to_string(), - }, - ) - .unwrap(); - - let vote = vote_resp.vote.unwrap(); - assert_eq!(vote.vote.option_id, 2); - assert_eq!(vote.rationale, None); -} - -#[test] -fn test_update_rationale() { - let mut app = App::default(); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: true, // Enable revoting - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "blue".to_string(), - amount: Uint128::new(100_000_000), - }, - Cw20Coin { - address: "elub".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart(core_addr, &dao_interface::msg::QueryMsg::DumpState {}) - .unwrap(); - let governance_modules = gov_state.proposal_modules; - - assert_eq!(governance_modules.len(), 1); - let govmod = governance_modules.into_iter().next().unwrap().address; - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title 1".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title 2".to_string(), - }, - ]; - - // Propose something - let mc_options = MultipleChoiceOptions { options }; - app.execute_contract( - Addr::unchecked(CREATOR_ADDR), - govmod.clone(), - &ExecuteMsg::Propose { - title: "A proposal".to_string(), - description: "A simple proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // Vote with rationale - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: Some("I think this is a good idea".to_string()), - }, - &[], - ) - .unwrap(); - - // Query rationale - let vote_resp: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod.clone(), - &QueryMsg::GetVote { - proposal_id: 1, - voter: "blue".to_string(), - }, - ) - .unwrap(); - - let vote = vote_resp.vote.unwrap(); - assert_eq!(vote.vote.option_id, 0); - assert_eq!( - vote.rationale, - Some("I think this is a good idea".to_string()) - ); - - // Update rationale - app.execute_contract( - Addr::unchecked("blue"), - govmod.clone(), - &ExecuteMsg::UpdateRationale { - proposal_id: 1, - rationale: Some("This may be a good idea, but I'm not sure. YOLO".to_string()), - }, - &[], - ) - .unwrap(); +fn remove_vote_hook_fails_for_no_proposal_hook() { + let CommonTest { + mut app, + proposal_multiple_contract_info, + } = setup_test(DAO_ADDR); - // Query rationale - let vote_resp: VoteResponse = app - .wrap() - .query_wasm_smart( - govmod, - &QueryMsg::GetVote { - proposal_id: 1, - voter: "blue".to_string(), - }, - ) - .unwrap(); - - let vote = vote_resp.vote.unwrap(); - assert_eq!(vote.vote.option_id, 0); - assert_eq!( - vote.rationale, - Some("This may be a good idea, but I'm not sure. YOLO".to_string()) - ); -} - -#[test] -fn test_open_proposal_passes_with_zero_timelock_veto_duration() { - let mut app = App::default(); - let timelock_duration = 0; - let veto_config = VetoConfig { - timelock_duration: Duration::Height(timelock_duration), - vetoer: "vetoer".to_string(), - early_execute: false, - veto_before_passed: true, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // zero duration timelock goes straight to passed status - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // pass enough time to expire the proposal voting - app.update_block(|b| b.height += 7); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Passed {},); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::VetoError(VetoError::TimelockExpired {})); -} - -#[test] -fn test_veto_non_existing_prop_id() { - let mut app = App::default(); - let timelock_duration = 0; - let veto_config = VetoConfig { - timelock_duration: Duration::Height(timelock_duration), - vetoer: "vetoer".to_string(), - early_execute: false, - veto_before_passed: true, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - // veto from non open/passed/veto state should return an error - let err: ContractError = app - .execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id: 69 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::NoSuchProposal { id: 69 }); -} - -#[test] -fn test_veto_with_no_veto_configuration() { - let mut app = App::default(); - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: None, - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - // veto from non open/passed/veto state should return an error - let err: ContractError = app - .execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!( - err, - ContractError::VetoError(VetoError::NoVetoConfiguration {}) - ); -} - -#[test] -fn test_veto_open_prop_with_veto_before_passed_disabled() { - let mut app = App::default(); - let timelock_duration = 10; - let veto_config = VetoConfig { - timelock_duration: Duration::Height(timelock_duration), - vetoer: "vetoer".to_string(), - early_execute: false, - veto_before_passed: false, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-2"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Open {},); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!( - err, - ContractError::VetoError(VetoError::NoVetoBeforePassed {}) - ); -} - -#[test] -fn test_veto_when_veto_timelock_expired() -> anyhow::Result<()> { - let mut app = App::default(); - let timelock_duration = Duration::Height(3); - let veto_config = VetoConfig { - timelock_duration, - vetoer: "vetoer".to_string(), - early_execute: false, - veto_before_passed: false, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal.proposal.expiration.add(timelock_duration)?, - }, - ); - - // pass enough time to expire the timelock - app.update_block(|b| b.height += 10); - - let err: ContractError = app - .execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::VetoError(VetoError::TimelockExpired {}),); - - Ok(()) -} - -#[test] -fn test_veto_sets_prop_status_to_vetoed() -> anyhow::Result<()> { - let mut app = App::default(); - let timelock_duration = Duration::Height(3); - let veto_config = VetoConfig { - timelock_duration, - vetoer: "vetoer".to_string(), - early_execute: false, - veto_before_passed: false, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal.proposal.expiration.add(timelock_duration)?, - }, - ); - - app.execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!(proposal.proposal.status, Status::Vetoed {},); - - Ok(()) -} - -#[test] -fn test_veto_from_catchall_state() { - let mut app = App::default(); - let timelock_duration = 3; - let veto_config = VetoConfig { - timelock_duration: Duration::Height(timelock_duration), - vetoer: "vetoer".to_string(), - early_execute: true, - veto_before_passed: false, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - // pass enough time to expire the timelock - app.update_block(|b| b.height += 10); - - app.execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Executed {},); - - // veto from non open/passed/veto state should return an error - let err: ContractError = app - .execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Veto { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!( - err, - ContractError::VetoError(VetoError::InvalidProposalStatus { - status: "executed".to_string(), - }) - ); -} - -#[test] -fn test_veto_timelock_early_execute_happy() -> anyhow::Result<()> { - let mut app = App::default(); - let timelock_duration = Duration::Height(3); - let veto_config = VetoConfig { - timelock_duration, - vetoer: "vetoer".to_string(), - early_execute: true, - veto_before_passed: false, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: true, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal.proposal.expiration.add(timelock_duration)?, - }, - ); - - // first we try unauthorized early execution - let err: ContractError = app - .execute_contract( - Addr::unchecked("not-the-vetoer"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - - assert_eq!(err, ContractError::Unauthorized {}); - - app.execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Executed {},); - - Ok(()) -} - -#[test] -fn test_veto_timelock_expires_happy() -> anyhow::Result<()> { - let mut app = App::default(); - let timelock_duration = Duration::Height(3); - let veto_config = VetoConfig { - timelock_duration, - vetoer: "vetoer".to_string(), - early_execute: false, - veto_before_passed: false, - }; - - let core_addr = instantiate_with_staked_balances_governance( - &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: false, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { - expiration: proposal.proposal.expiration.add(timelock_duration)?, - }, - ); - - // pass enough time to expire the timelock - app.update_block(|b| b.height += 10); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Executed {},); - - Ok(()) -} - -#[test] -fn test_veto_only_members_execute_proposal() -> anyhow::Result<()> { - let mut app = App::default(); - let timelock_duration = Duration::Height(3); - let veto_config = VetoConfig { - timelock_duration, - vetoer: "vetoer".to_string(), - early_execute: true, - veto_before_passed: false, - }; - - let core_addr = instantiate_with_staked_balances_governance( + // remove vote hook fails + remove_vote_hook_should_fail( &mut app, - InstantiateMsg { - min_voting_period: None, - max_voting_period: Duration::Height(6), - only_members_execute: true, - allow_revoting: false, - voting_strategy: VotingStrategy::SingleChoice { - quorum: PercentageThreshold::Majority {}, - }, - close_proposal_on_execution_failure: false, - pre_propose_info: PreProposeInfo::AnyoneMayPropose {}, - veto: Some(veto_config), - }, - Some(vec![ - Cw20Coin { - address: "a-1".to_string(), - amount: Uint128::new(110_000_000), - }, - Cw20Coin { - address: "a-2".to_string(), - amount: Uint128::new(100_000_000), - }, - ]), - ); - let govmod = query_multiple_proposal_module(&app, &core_addr); - - let proposal_module = query_multiple_proposal_module(&app, &core_addr); - - let next_proposal_id: u64 = app - .wrap() - .query_wasm_smart(&proposal_module, &QueryMsg::NextProposalId {}) - .unwrap(); - assert_eq!(next_proposal_id, 1); - - let options = vec![ - MultipleChoiceOption { - description: "multiple choice option 1".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - MultipleChoiceOption { - description: "multiple choice option 2".to_string(), - msgs: vec![], - title: "title".to_string(), - }, - ]; - let mc_options = MultipleChoiceOptions { options }; - - // Create a basic proposal with 2 options - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Propose { - title: "A simple text proposal".to_string(), - description: "A simple text proposal".to_string(), - choices: mc_options, - proposer: None, - }, - &[], - ) - .unwrap(); - - app.execute_contract( - Addr::unchecked("a-1"), - proposal_module.clone(), - &ExecuteMsg::Vote { - proposal_id: 1, - vote: MultipleChoiceVote { option_id: 0 }, - rationale: None, - }, - &[], - ) - .unwrap(); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - - let expiration = proposal.proposal.expiration.add(timelock_duration)?; - assert_eq!( - proposal.proposal.status, - Status::VetoTimelock { expiration }, + &proposal_multiple_contract_info.address, + proposal_multiple_contract_info.code_hash, + DAO_ADDR, + "vote_hook_addr", + "vote_hook_code_hash", ); - - app.update_block(|b| b.height += 10); - // assert timelock is expired - assert!(expiration.is_expired(&app.block_info())); - - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Passed); - - // Proposal cannot be executed by vetoer once timelock expired - let err: ContractError = app - .execute_contract( - Addr::unchecked("vetoer"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap_err() - .downcast() - .unwrap(); - assert_eq!(err, ContractError::Unauthorized {}); - - // Proposal can be executed by member once timelock expired - app.execute_contract( - Addr::unchecked("a-2"), - proposal_module.clone(), - &ExecuteMsg::Execute { proposal_id: 1 }, - &[], - ) - .unwrap(); - let proposal: ProposalResponse = query_proposal(&app, &govmod, 1); - assert_eq!(proposal.proposal.status, Status::Executed {},); - - Ok(()) } diff --git a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs deleted file mode 100644 index 0b69da2..0000000 --- a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs +++ /dev/null @@ -1,387 +0,0 @@ -// use crate::msg::InstantiateMsg; -// use crate::testing::instantiate::get_pre_propose_info; -// use crate::testing::{ -// execute::{ -// close_proposal, execute_proposal, execute_proposal_should_fail, make_proposal, mint_cw20s, -// vote_on_proposal, -// }, -// instantiate::{ -// get_default_token_dao_proposal_module_instantiate, -// instantiate_with_staked_balances_governance, -// }, -// queries::{query_balance_cw20, query_dao_token, query_proposal, query_single_proposal_module}, -// }; -// use cosmwasm_std::{to_binary, Addr, ContractInfo, CosmosMsg, Decimal, Uint128, WasmMsg}; -// use dao_voting::{ -// deposit::{DepositRefundPolicy, UncheckedDepositInfo, VotingModuleTokenType}, -// status::Status, -// threshold::{PercentageThreshold, Threshold::AbsolutePercentage}, -// voting::Vote, -// }; -// use secret_multi_test::{next_block, App}; -// use secret_utils::Duration; -// use snip20_reference_impl::msg::InitialBalance; - -// use super::CREATOR_ADDR; -// use crate::{query::ProposalResponse, ContractError}; - -// struct CommonTest { -// app: App, -// proposal_module: ContractInfo, -// proposal_id: u64, -// } -// fn setup_test(messages: Vec) -> CommonTest { -// let mut app = App::default(); -// let instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); -// let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); -// let proposal_module = query_single_proposal_module(&app, &core_addr); -// let gov_token = query_dao_token(&app, &core_addr); - -// // Mint some tokens to pay the proposal deposit. -// mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); -// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, messages); - -// CommonTest { -// app, -// proposal_module, -// proposal_id, -// } -// } - -// // A proposal that is still accepting votes (is open) cannot -// // be executed. Any attempts to do so should fail and return -// // an error. -// #[test] -// fn test_execute_proposal_open() { -// let CommonTest { -// mut app, -// proposal_module, -// proposal_id, -// } = setup_test(vec![]); - -// app.update_block(next_block); - -// // assert proposal is open -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Open); - -// // attempt to execute and assert that it fails -// let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); -// assert!(matches!(err, ContractError::NotPassed {})) -// } - -// // A proposal can be executed if and only if it passed. -// // Any attempts to execute a proposal that has been rejected -// // or closed (after rejection) should fail and return an error. -// #[test] -// fn test_execute_proposal_rejected_closed() { -// let CommonTest { -// mut app, -// proposal_module, -// proposal_id, -// } = setup_test(vec![]); - -// // Assert proposal is open and vote enough to reject it -// let proposal: ProposalResponse = query_proposal(&app, &proposal_module, 1); -// assert_eq!(proposal.proposal.status, Status::Open); -// vote_on_proposal( -// &mut app, -// &proposal_module, -// CREATOR_ADDR, -// proposal_id, -// Vote::No, -// ); - -// app.update_block(next_block); - -// // Assert proposal is rejected -// let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Rejected); - -// // Attempt to execute -// let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); -// assert!(matches!(err, ContractError::NotPassed {})); - -// app.update_block(next_block); - -// // close the proposal -// close_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Closed); - -// // Attempt to execute -// let err = execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); -// assert!(matches!(err, ContractError::NotPassed {})) -// } - -// // A proposal can only be executed once. Any subsequent -// // attempts to execute it should fail and return an error. -// #[test] -// fn test_execute_proposal_more_than_once() { -// let CommonTest { -// mut app, -// proposal_module, -// proposal_id, -// } = setup_test(vec![]); - -// // Assert proposal is open and vote enough to reject it -// let proposal: ProposalResponse = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Open); -// vote_on_proposal( -// &mut app, -// &proposal_module, -// CREATOR_ADDR, -// proposal_id, -// Vote::Yes, -// ); - -// app.update_block(next_block); - -// // assert proposal is passed, execute it -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Passed); -// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - -// app.update_block(next_block); - -// // assert proposal executed and attempt to execute it again -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Executed); -// let err: ContractError = -// execute_proposal_should_fail(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); -// assert!(matches!(err, ContractError::NotPassed {})); -// } - -// // After proposal is executed, no subsequent votes -// // should change the status of the proposal, even if -// // the votes should shift to the opposing direction. -// #[test] -// pub fn test_executed_prop_state_remains_after_vote_swing() { -// let mut app = App::default(); - -// let instantiate = InstantiateMsg { -// veto: None, -// threshold: AbsolutePercentage { -// percentage: PercentageThreshold::Percent(Decimal::percent(15)), -// }, -// max_voting_period: Duration::Time(604800), // One week. -// min_voting_period: None, -// only_members_execute: true, -// allow_revoting: false, -// pre_propose_info: get_pre_propose_info( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: dao_voting::deposit::DepositToken::VotingModuleToken { -// token_type: VotingModuleTokenType::Cw20, -// }, -// amount: Uint128::new(10_000_000), -// refund_policy: DepositRefundPolicy::OnlyPassed, -// }), -// false, -// ), -// close_proposal_on_execution_failure: true, -// dao_code_hash: "dao_code_hash".to_string(), -// query_auth: todo!(), -// }; - -// let core_addr = instantiate_with_staked_balances_governance( -// &mut app, -// instantiate, -// Some(vec![ -// InitialBalance { -// address: "threshold".to_string(), -// amount: Uint128::new(20), -// }, -// InitialBalance { -// address: CREATOR_ADDR.to_string(), -// amount: Uint128::new(50), -// }, -// InitialBalance { -// address: "overslept_vote".to_string(), -// amount: Uint128::new(30), -// }, -// ]), -// ); -// let proposal_module = query_single_proposal_module(&app, &core_addr); -// let gov_token = query_dao_token(&app, &core_addr); - -// mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); -// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); - -// // someone quickly votes, proposal gets executed -// vote_on_proposal( -// &mut app, -// &proposal_module, -// "threshold", -// proposal_id, -// Vote::Yes, -// ); -// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - -// app.update_block(next_block); - -// // assert prop is executed prior to its expiry -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Executed); -// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); -// assert!(!proposal.proposal.expiration.is_expired(&app.block_info())); - -// // someone wakes up and casts their vote to express their -// // opinion (not affecting the result of proposal) -// vote_on_proposal( -// &mut app, -// &proposal_module, -// CREATOR_ADDR, -// proposal_id, -// Vote::No, -// ); -// vote_on_proposal( -// &mut app, -// &proposal_module, -// "overslept_vote", -// proposal_id, -// Vote::No, -// ); - -// app.update_block(next_block); - -// // assert that everyone's votes are reflected in the proposal -// // and proposal remains in executed state -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Executed); -// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); -// assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); -// } - -// // After reaching a passing state, no subsequent votes -// // should change the status of the proposal, even if -// // the votes should shift to the opposing direction. -// #[test] -// pub fn test_passed_prop_state_remains_after_vote_swing() { -// let mut app = App::default(); - -// let instantiate = InstantiateMsg { -// veto: None, -// threshold: AbsolutePercentage { -// percentage: PercentageThreshold::Percent(Decimal::percent(15)), -// }, -// max_voting_period: Duration::Time(604800), // One week. -// min_voting_period: None, -// only_members_execute: true, -// allow_revoting: false, -// pre_propose_info: get_pre_propose_info( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: dao_voting::deposit::DepositToken::VotingModuleToken { -// token_type: VotingModuleTokenType::Cw20, -// }, -// amount: Uint128::new(10_000_000), -// refund_policy: DepositRefundPolicy::OnlyPassed, -// }), -// false, -// ), -// close_proposal_on_execution_failure: true, -// dao_code_hash: todo!(), -// query_auth: todo!(), -// }; - -// let core_addr = instantiate_with_staked_balances_governance( -// &mut app, -// instantiate, -// Some(vec![ -// Cw20Coin { -// address: "threshold".to_string(), -// amount: Uint128::new(20), -// }, -// Cw20Coin { -// address: CREATOR_ADDR.to_string(), -// amount: Uint128::new(50), -// }, -// Cw20Coin { -// address: "overslept_vote".to_string(), -// amount: Uint128::new(30), -// }, -// ]), -// ); -// let proposal_module = query_single_proposal_module(&app, &core_addr); -// let gov_token = query_dao_token(&app, &core_addr); - -// // if the proposal passes, it should mint 100_000_000 tokens to "threshold" -// let msg = cw20::Cw20ExecuteMsg::Mint { -// recipient: "threshold".to_string(), -// amount: Uint128::new(100_000_000), -// }; -// let binary_msg = to_binary(&msg).unwrap(); - -// mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); -// let proposal_id = make_proposal( -// &mut app, -// &proposal_module, -// CREATOR_ADDR, -// vec![WasmMsg::Execute { -// contract_addr: gov_token.to_string(), -// msg: binary_msg, -// funds: vec![], -// } -// .into()], -// ); - -// // assert that the initial "threshold" address balance is 0 -// let balance = query_balance_cw20(&app, gov_token.to_string(), "threshold"); -// assert_eq!(balance, Uint128::zero()); - -// // vote enough to pass the proposal -// vote_on_proposal( -// &mut app, -// &proposal_module, -// "threshold", -// proposal_id, -// Vote::Yes, -// ); - -// // assert proposal is passed with 20 votes in favor and none opposed -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Passed); -// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); -// assert_eq!(proposal.proposal.votes.no, Uint128::zero()); - -// app.update_block(next_block); - -// // the other voters wake up, vote against the proposal -// vote_on_proposal( -// &mut app, -// &proposal_module, -// CREATOR_ADDR, -// proposal_id, -// Vote::No, -// ); -// vote_on_proposal( -// &mut app, -// &proposal_module, -// "overslept_vote", -// proposal_id, -// Vote::No, -// ); - -// app.update_block(next_block); - -// // assert that the late votes have been counted and proposal -// // is still in passed state before executing it -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Passed); -// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); -// assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); - -// execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); - -// app.update_block(next_block); - -// // make sure that the initial "threshold" address balance is -// // 100_000_000 and late votes did not make a difference -// let proposal = query_proposal(&app, &proposal_module, proposal_id); -// assert_eq!(proposal.proposal.status, Status::Executed); -// assert_eq!(proposal.proposal.votes.yes, Uint128::new(20)); -// assert_eq!(proposal.proposal.votes.no, Uint128::new(80)); -// let balance = query_balance_cw20(&app, gov_token.to_string(), "threshold"); -// assert_eq!(balance, Uint128::new(100_000_000)); -// } diff --git a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs index 668fdd2..0b6ccdb 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/contracts.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/contracts.rs @@ -1,44 +1,6 @@ use cosmwasm_std::Empty; - -use dao_pre_propose_single as cppbps; use secret_multi_test::{Contract, ContractWrapper}; -pub(crate) fn snip20_base_contract() -> Box> { - let contract = ContractWrapper::new( - snip20_reference_impl::contract::execute, - snip20_reference_impl::contract::instantiate, - snip20_reference_impl::contract::query, - ); - Box::new(contract) -} - -pub(crate) fn cw4_group_contract() -> Box> { - let contract = ContractWrapper::new( - cw4_group::contract::execute, - cw4_group::contract::instantiate, - cw4_group::contract::query, - ); - Box::new(contract) -} - -pub(crate) fn snip721_base_contract() -> Box> { - let contract = ContractWrapper::new( - snip721_reference_impl::contract::execute, - snip721_reference_impl::contract::instantiate, - snip721_reference_impl::contract::query, - ); - Box::new(contract) -} - -pub(crate) fn snip20_stake_contract() -> Box> { - let contract = ContractWrapper::new( - snip20_stake::contract::execute, - snip20_stake::contract::instantiate, - snip20_stake::contract::query, - ); - Box::new(contract) -} - pub(crate) fn proposal_single_contract() -> Box> { let contract = ContractWrapper::new( crate::contract::execute, @@ -50,63 +12,6 @@ pub(crate) fn proposal_single_contract() -> Box> { Box::new(contract) } -pub(crate) fn pre_propose_single_contract() -> Box> { - let contract = ContractWrapper::new( - cppbps::contract::execute, - cppbps::contract::instantiate, - cppbps::contract::query, - ); - Box::new(contract) -} - -pub(crate) fn snip20_staked_balances_voting_contract() -> Box> { - let contract = ContractWrapper::new( - dao_voting_snip20_staked::contract::execute, - dao_voting_snip20_staked::contract::instantiate, - dao_voting_snip20_staked::contract::query, - ) - .with_reply(dao_voting_snip20_staked::contract::reply); - Box::new(contract) -} - -pub(crate) fn native_staked_balances_voting_contract() -> Box> { - let contract = ContractWrapper::new( - dao_voting_token_staked::contract::execute, - dao_voting_token_staked::contract::instantiate, - dao_voting_token_staked::contract::query, - ); - Box::new(contract) -} - -pub(crate) fn snip721_stake_contract() -> Box> { - let contract = ContractWrapper::new( - dao_voting_snip721_staked::contract::execute, - dao_voting_snip721_staked::contract::instantiate, - dao_voting_snip721_staked::contract::query, - ); - Box::new(contract) -} - -pub(crate) fn cw_core_contract() -> Box> { - let contract = ContractWrapper::new( - dao_dao_core::contract::execute, - dao_dao_core::contract::instantiate, - dao_dao_core::contract::query, - ) - .with_reply(dao_dao_core::contract::reply); - Box::new(contract) -} - -pub(crate) fn cw4_voting_contract() -> Box> { - let contract = ContractWrapper::new( - dao_voting_cw4::contract::execute, - dao_voting_cw4::contract::instantiate, - dao_voting_cw4::contract::query, - ) - .with_reply(dao_voting_cw4::contract::reply); - Box::new(contract) -} - pub(crate) fn query_auth_contract() -> Box> { let contract = ContractWrapper::new( query_auth::contract::execute, diff --git a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs b/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs deleted file mode 100644 index ec8f97a..0000000 --- a/contracts/proposal/dao-proposal-single/src/testing/do_votes.rs +++ /dev/null @@ -1,410 +0,0 @@ -// use std::mem::discriminant; - -// use cosmwasm_std::{coins, Addr, Coin, ContractInfo, Uint128}; -// use dao_interface::state::ProposalModule; -// use dao_pre_propose_single as cppbps; -// use secret_multi_test::{App, BankSudo, Executor, SudoMsg}; - -// use cw_denom::CheckedDenom; -// use dao_testing::{ShouldExecute, TestSingleChoiceVote}; -// use dao_voting::{ -// deposit::{CheckedDepositInfo, UncheckedDepositInfo}, -// status::Status, -// threshold::Threshold, -// }; -// use shade_protocol::utils::asset::RawContract; -// use snip20_reference_impl::msg::InitialBalance; - -// use crate::{ -// msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, -// query::{ProposalResponse, VoteInfo, VoteResponse}, -// testing::{instantiate::*, queries::query_deposit_config_and_pre_propose_module}, -// }; - -// pub(crate) fn do_votes_staked_balances( -// votes: Vec, -// threshold: Threshold, -// expected_status: Status, -// total_supply: Option, -// ) { -// do_test_votes( -// votes, -// threshold, -// expected_status, -// total_supply, -// None::, -// instantiate_with_staked_balances_governance, -// ); -// } - -// pub(crate) fn do_votes_nft_balances( -// votes: Vec, -// threshold: Threshold, -// expected_status: Status, -// total_supply: Option, -// ) { -// do_test_votes( -// votes, -// threshold, -// expected_status, -// total_supply, -// None, -// instantiate_with_staked_cw721_governance, -// ); -// } - -// pub(crate) fn do_votes_native_staked_balances( -// votes: Vec, -// threshold: Threshold, -// expected_status: Status, -// total_supply: Option, -// ) { -// do_test_votes( -// votes, -// threshold, -// expected_status, -// total_supply, -// None, -// instantiate_with_native_staked_balances_governance, -// ); -// } - -// pub(crate) fn do_votes_cw4_weights( -// votes: Vec, -// threshold: Threshold, -// expected_status: Status, -// total_supply: Option, -// ) { -// do_test_votes( -// votes, -// threshold, -// expected_status, -// total_supply, -// None::, -// instantiate_with_cw4_groups_governance, -// ); -// } - -// fn do_test_votes( -// votes: Vec, -// threshold: Threshold, -// expected_status: Status, -// total_supply: Option, -// deposit_info: Option, -// setup_governance: F, -// ) -> (App, Addr) -// where -// F: Fn(&mut App, InstantiateMsg, Option>) -> ContractInfo, -// { -// let mut app = App::default(); - -// // Mint some ujuno so that it exists for native staking tests -// // Otherwise denom validation will fail -// app.sudo(SudoMsg::Bank(BankSudo::Mint { -// to_address: "sodenomexists".to_string(), -// amount: vec![Coin { -// amount: Uint128::new(10), -// denom: "ujuno".to_string(), -// }], -// })) -// .unwrap(); - -// let mut initial_balances = votes -// .iter() -// .map( -// |TestSingleChoiceVote { voter, weight, .. }| InitialBalance { -// address: voter.to_string(), -// amount: *weight, -// }, -// ) -// .collect::>(); -// let initial_balances_supply = votes.iter().fold(Uint128::zero(), |p, n| p + n.weight); -// let to_fill = total_supply.map(|total_supply| total_supply - initial_balances_supply); -// if let Some(fill) = to_fill { -// initial_balances.push(InitialBalance { -// address: "filler".to_string(), -// amount: fill, -// }) -// } - -// let pre_propose_info = get_pre_propose_info(&mut app, deposit_info, false); - -// let proposer = match votes.first() { -// Some(vote) => vote.voter.clone(), -// None => panic!("do_test_votes must have at least one vote."), -// }; - -// let query_auth = instantiate_query_auth(&mut app); - -// let max_voting_period = secret_utils::Duration::Height(6); -// let instantiate = InstantiateMsg { -// veto: None, -// threshold, -// max_voting_period, -// min_voting_period: None, -// only_members_execute: false, -// allow_revoting: false, -// close_proposal_on_execution_failure: true, -// pre_propose_info, -// dao_code_hash: "dao_code_hash".to_string(), -// query_auth: RawContract { -// address: query_auth.address.to_string(), -// code_hash: query_auth.code_hash, -// }, -// }; - -// let core_info = setup_governance(&mut app, instantiate, Some(initial_balances)); - -// let governance_modules: Vec = app -// .wrap() -// .query_wasm_smart( -// core_info.clone().code_hash, -// core_info.clone().address.to_string(), -// &dao_interface::msg::QueryMsg::ProposalModules { -// start_after: None, -// limit: None, -// }, -// ) -// .unwrap(); - -// assert_eq!(governance_modules.len(), 1); -// let proposal_single = governance_modules.into_iter().next().unwrap().address; - -// let (deposit_config, pre_propose_module) = -// query_deposit_config_and_pre_propose_module(&app, &proposal_single); -// // Pay the cw20 deposit if needed. -// if let Some(CheckedDepositInfo { -// denom: CheckedDenom::Cw20(ref token), -// amount, -// .. -// }) = deposit_config.deposit_info -// { -// app.execute_contract( -// Addr::unchecked(&proposer), -// token.clone(), -// &snip20_reference_impl::msg::ExecuteMsg::IncreaseAllowance { -// spender: pre_propose_module.to_string(), -// amount, -// expiration: None, -// padding: None, -// }, -// &[], -// ) -// .unwrap(); -// } - -// let funds = if let Some(CheckedDepositInfo { -// denom: CheckedDenom::Native(ref denom), -// amount, -// .. -// }) = deposit_config.deposit_info -// { -// // Mint the needed tokens to create the deposit. -// app.sudo(secret_multi_test::SudoMsg::Bank(BankSudo::Mint { -// to_address: proposer.clone(), -// amount: coins(amount.u128(), denom), -// })) -// .unwrap(); -// coins(amount.u128(), denom) -// } else { -// vec![] -// }; - -// app.execute_contract( -// Addr::unchecked(&proposer), -// pre_propose_module, -// &cppbps::ExecuteMsg::Propose { -// msg: cppbps::ProposeMessage::Propose { -// title: "A simple text proposal".to_string(), -// description: "This is a simple text proposal".to_string(), -// msgs: vec![], -// }, -// }, -// &funds, -// ) -// .unwrap(); - -// // Cast votes. -// for vote in votes { -// let TestSingleChoiceVote { -// voter, -// position, -// weight, -// should_execute, -// } = vote; -// // Vote on the proposal. -// let res = app.execute_contract( -// Addr::unchecked(voter.clone()), -// proposal_single.clone(), -// &ExecuteMsg::Vote { -// proposal_id: 1, -// vote: position, -// rationale: None, -// }, -// &[], -// ); -// match should_execute { -// ShouldExecute::Yes => { -// assert!(res.is_ok()); -// // Check that the vote was recorded correctly. -// let vote: VoteResponse = app -// .wrap() -// .query_wasm_smart( -// proposal_single.clone(), -// &QueryMsg::GetVote { -// proposal_id: 1, -// voter: voter.clone(), -// }, -// ) -// .unwrap(); -// let expected = VoteResponse { -// vote: Some(VoteInfo { -// rationale: None, -// voter: Addr::unchecked(&voter), -// vote: position, -// power: match deposit_config.deposit_info { -// Some(CheckedDepositInfo { -// amount, -// denom: CheckedDenom::Cw20(_), -// .. -// }) => { -// if proposer == voter { -// weight - amount -// } else { -// weight -// } -// } -// // Native token deposits shouldn't impact -// // expected voting power. -// _ => weight, -// }, -// }), -// }; -// assert_eq!(vote, expected) -// } -// ShouldExecute::No => { -// res.unwrap_err(); -// } -// ShouldExecute::Meh => (), -// } -// } - -// let proposal: ProposalResponse = app -// .wrap() -// .query_wasm_smart(proposal_single, &QueryMsg::Proposal { proposal_id: 1 }) -// .unwrap(); - -// // We just care about getting the right variant -// assert_eq!( -// discriminant::(&proposal.proposal.status), -// discriminant::(&expected_status) -// ); - -// (app, core_addr) -// } - -// #[test] -// fn test_vote_simple() { -// dao_testing::test_simple_votes(do_votes_cw4_weights); -// dao_testing::test_simple_votes(do_votes_staked_balances); -// dao_testing::test_simple_votes(do_votes_nft_balances); -// dao_testing::test_simple_votes(do_votes_native_staked_balances) -// } - -// #[test] -// fn test_simple_vote_no_overflow() { -// dao_testing::test_simple_vote_no_overflow(do_votes_staked_balances); -// dao_testing::test_simple_vote_no_overflow(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_vote_no_overflow() { -// dao_testing::test_vote_no_overflow(do_votes_staked_balances); -// dao_testing::test_vote_no_overflow(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_simple_early_rejection() { -// dao_testing::test_simple_early_rejection(do_votes_cw4_weights); -// dao_testing::test_simple_early_rejection(do_votes_staked_balances); -// dao_testing::test_simple_early_rejection(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_vote_abstain_only() { -// dao_testing::test_vote_abstain_only(do_votes_cw4_weights); -// dao_testing::test_vote_abstain_only(do_votes_staked_balances); -// dao_testing::test_vote_abstain_only(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_tricky_rounding() { -// dao_testing::test_tricky_rounding(do_votes_cw4_weights); -// dao_testing::test_tricky_rounding(do_votes_staked_balances); -// dao_testing::test_tricky_rounding(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_no_double_votes() { -// dao_testing::test_no_double_votes(do_votes_cw4_weights); -// dao_testing::test_no_double_votes(do_votes_staked_balances); -// dao_testing::test_no_double_votes(do_votes_nft_balances); -// dao_testing::test_no_double_votes(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_votes_favor_yes() { -// dao_testing::test_votes_favor_yes(do_votes_staked_balances); -// dao_testing::test_votes_favor_yes(do_votes_nft_balances); -// dao_testing::test_votes_favor_yes(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_votes_low_threshold() { -// dao_testing::test_votes_low_threshold(do_votes_cw4_weights); -// dao_testing::test_votes_low_threshold(do_votes_staked_balances); -// dao_testing::test_votes_low_threshold(do_votes_nft_balances); -// dao_testing::test_votes_low_threshold(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_majority_vs_half() { -// dao_testing::test_majority_vs_half(do_votes_cw4_weights); -// dao_testing::test_majority_vs_half(do_votes_staked_balances); -// dao_testing::test_majority_vs_half(do_votes_nft_balances); -// dao_testing::test_majority_vs_half(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_pass_threshold_not_quorum() { -// dao_testing::test_pass_threshold_not_quorum(do_votes_cw4_weights); -// dao_testing::test_pass_threshold_not_quorum(do_votes_staked_balances); -// dao_testing::test_pass_threshold_not_quorum(do_votes_nft_balances); -// dao_testing::test_pass_threshold_not_quorum(do_votes_native_staked_balances); -// } - -// #[test] -// fn test_pass_threshold_exactly_quorum() { -// dao_testing::test_pass_exactly_quorum(do_votes_cw4_weights); -// dao_testing::test_pass_exactly_quorum(do_votes_staked_balances); -// dao_testing::test_pass_exactly_quorum(do_votes_nft_balances); -// dao_testing::test_pass_exactly_quorum(do_votes_native_staked_balances); -// } - -// /// Generate some random voting selections and make sure they behave -// /// as expected. We split this test up as these take a while and cargo -// /// can parallize tests. -// #[test] -// fn fuzz_voting_cw4_weights() { -// dao_testing::fuzz_voting(do_votes_cw4_weights) -// } - -// #[test] -// fn fuzz_voting_staked_balances() { -// dao_testing::fuzz_voting(do_votes_staked_balances) -// } - -// #[test] -// fn fuzz_voting_native_staked_balances() { -// dao_testing::fuzz_voting(do_votes_native_staked_balances) -// } diff --git a/contracts/proposal/dao-proposal-single/src/testing/execute.rs b/contracts/proposal/dao-proposal-single/src/testing/execute.rs index a62590c..7b44475 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/execute.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/execute.rs @@ -1,12 +1,9 @@ -use cosmwasm_std::{ - from_binary, to_binary, Addr, Coin, ContractInfo, CosmosMsg, Decimal, MessageInfo, Uint128, -}; -use secret_multi_test::{App, BankSudo, Executor}; +use cosmwasm_std::{from_binary, Addr, ContractInfo, CosmosMsg, Decimal, MessageInfo}; +use secret_multi_test::{App, Executor}; use dao_voting::voting::Vote; use secret_utils::Duration; use shade_protocol::{basic_staking::Auth, utils::asset::RawContract}; -use snip20_reference_impl::msg::InitialBalance; use crate::{ msg::{ExecuteMsg, QueryMsg}, @@ -15,8 +12,6 @@ use crate::{ ContractError, }; -use super::{contracts::snip20_base_contract, CREATOR_ADDR}; - // Creates a proposal then checks that the proposal was created with // the specified messages and returns the ID of the proposal. // @@ -424,70 +419,6 @@ pub(crate) fn execute_veto_fails( .unwrap() } -pub(crate) fn _mint_natives(app: &mut App, receiver: &str, amount: Vec) { - app.sudo(secret_multi_test::SudoMsg::Bank(BankSudo::Mint { - to_address: receiver.to_string(), - amount, - })) - .unwrap(); -} - -pub(crate) fn _mint_snip20s( - app: &mut App, - snip20_contract: &Addr, - snip20_contract_code_hash: String, - sender: &Addr, - receiver: &str, - amount: u128, -) { - app.execute_contract( - sender.clone(), - &ContractInfo { - address: snip20_contract.clone(), - code_hash: snip20_contract_code_hash, - }, - &snip20_reference_impl::msg::ExecuteMsg::Mint { - recipient: receiver.to_string(), - amount: Uint128::new(amount), - memo: None, - decoys: None, - entropy: None, - padding: None, - }, - &[], - ) - .unwrap(); -} - -pub(crate) fn _instantiate_snip20_base_default( - app: &mut App, - admin: Option, -) -> ContractInfo { - let snip20_contract_instantiate_info = app.store_code(snip20_base_contract()); - let snip20_instantiate = snip20_reference_impl::msg::InstantiateMsg { - name: "snip20 token".to_string(), - symbol: "cwtwenty".to_string(), - decimals: 6, - initial_balances: Some(vec![InitialBalance { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(10_000_000), - }]), - admin, - prng_seed: to_binary(&"prng_seed".to_string()).unwrap(), - config: None, - supported_denoms: None, - }; - app.instantiate_contract( - snip20_contract_instantiate_info, - Addr::unchecked("ekez"), - &snip20_instantiate, - &[], - "snip20-base", - None, - ) - .unwrap() -} - pub(crate) fn add_proposal_hook( app: &mut App, proposal_module: &Addr, diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 1055cba..35e7d37 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -1,58 +1,16 @@ -use cosmwasm_std::{to_binary, Addr, Coin, ContractInfo, Decimal, Empty, Uint128}; - -use dao_interface::state::{Admin, AnyContractInfo, ModuleInstantiateInfo}; -use dao_pre_propose_single as cppbps; -use secret_multi_test::{next_block, App, BankSudo, ContractInstantiationInfo, Executor, SudoMsg}; +use cosmwasm_std::{to_binary, Addr, ContractInfo, Decimal}; +use secret_multi_test::{App, Executor}; use secret_utils::Duration; use dao_voting::{ - deposit::UncheckedDepositInfo, pre_propose::PreProposeInfo, - threshold::{ActiveThreshold, PercentageThreshold, Threshold::ThresholdQuorum}, + threshold::{PercentageThreshold, Threshold::ThresholdQuorum}, }; -use dao_voting_cw4::msg::GroupContract; -use dao_voting_snip20_staked::snip20_msg::InitialBalance as InitialBalanceSnip20Staked; use shade_protocol::utils::asset::RawContract; -use snip20_reference_impl::msg::InitialBalance; -use snip721_reference_impl::msg::ReceiverInfo; use crate::msg::InstantiateMsg; -use super::{ - contracts::{ - cw4_group_contract, cw4_voting_contract, cw_core_contract, - native_staked_balances_voting_contract, proposal_single_contract, query_auth_contract, - snip20_base_contract, snip20_stake_contract, snip20_staked_balances_voting_contract, - snip721_base_contract, snip721_stake_contract, - }, - CREATOR_ADDR, -}; - -pub(crate) fn get_pre_propose_info( - app: &mut App, - deposit_info: Option, - open_proposal_submission: bool, - proposal_module_code_hash: String, -) -> PreProposeInfo { - let pre_propose_contract = - app.store_code(crate::testing::contracts::pre_propose_single_contract()); - PreProposeInfo::ModuleMayPropose { - info: ModuleInstantiateInfo { - code_id: pre_propose_contract.code_id, - code_hash: pre_propose_contract.code_hash, - msg: to_binary(&cppbps::InstantiateMsg { - deposit_info, - open_proposal_submission, - extension: Empty::default(), - proposal_module_code_hash, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "pre_propose_contract".to_string(), - }, - } -} +use super::{contracts::query_auth_contract, CREATOR_ADDR}; pub(crate) fn get_default_token_dao_proposal_module_instantiate( query_auth: RawContract, @@ -75,638 +33,6 @@ pub(crate) fn get_default_token_dao_proposal_module_instantiate( } } -// Same as above but no proposal deposit. -pub(crate) fn _get_default_non_token_dao_proposal_module_instantiate( - app: &mut App, - proposal_module_code_hash: String, - query_auth: RawContract, - dao_code_hash: String, -) -> InstantiateMsg { - InstantiateMsg { - veto: None, - threshold: ThresholdQuorum { - threshold: PercentageThreshold::Percent(Decimal::percent(15)), - quorum: PercentageThreshold::Majority {}, - }, - max_voting_period: Duration::Time(604800), // One week. - min_voting_period: None, - only_members_execute: true, - allow_revoting: false, - pre_propose_info: get_pre_propose_info(app, None, false, proposal_module_code_hash), - close_proposal_on_execution_failure: true, - dao_code_hash, - query_auth, - } -} - -pub(crate) fn _instantiate_with_staked_snip721_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, - dao_code_hash: String, - query_auth: RawContract, -) -> ContractInfo { - let proposal_module_info = app.store_code(proposal_single_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![InitialBalance { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|InitialBalance { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let snip721_info = app.store_code(snip721_base_contract()); - let snip721_stake_info = app.store_code(snip721_stake_contract()); - let core_contract_info = app.store_code(cw_core_contract()); - - let nft_info = app - .instantiate_contract( - snip721_info, - Addr::unchecked("ekez"), - &snip721_reference_impl::msg::InstantiateMsg { - admin: Some("ekez".to_string()), - symbol: "token".to_string(), - name: "ekez token best token".to_string(), - entropy: "entropy".to_string(), - royalty_info: None, - config: None, - post_init_callback: None, - }, - &[], - "nft-staking", - None, - ) - .unwrap(); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - dao_uri: None, - image_url: None, - automatically_add_snip20s: true, - automatically_add_snip721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: snip721_stake_info.code_id, - code_hash: snip721_stake_info.code_hash, - msg: to_binary(&dao_voting_snip721_staked::msg::InstantiateMsg { - unstaking_duration: None, - nft_contract: dao_voting_snip721_staked::msg::NftContract::Existing { - address: nft_info.clone().address.to_string(), - code_hash: nft_info.clone().code_hash, - }, - dao_code_hash, - query_auth, - active_threshold: None, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_info.code_id, - code_hash: proposal_module_info.code_hash, - msg: to_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), - }; - - let core_contract = app - .instantiate_contract( - core_contract_info, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let core_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_contract.clone().code_hash, - core_contract.clone().address.to_string(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let staking_info = core_state.voting_module; - - for InitialBalance { address, amount } in initial_balances { - for i in 0..amount.u128() { - app.execute_contract( - Addr::unchecked("ekez"), - &nft_info.clone(), - &snip721_reference_impl::msg::ExecuteMsg::MintNft { - token_id: Some(format!("{address}_{i}")), - owner: Some(address.clone()), - public_metadata: None, - private_metadata: None, - serial_number: None, - royalty_info: None, - transferable: Some(true), - memo: None, - padding: None, - }, - &[], - ) - .unwrap(); - app.execute_contract( - Addr::unchecked(address.clone()), - &nft_info.clone(), - &snip721_reference_impl::msg::ExecuteMsg::SendNft { - contract: staking_info.clone().addr.to_string(), - token_id: format!("{address}_{i}"), - msg: Some(to_binary("").unwrap()), - receiver_info: Some(ReceiverInfo { - recipient_code_hash: staking_info.clone().code_hash, - also_implements_batch_receive_nft: Some(true), - }), - memo: None, - padding: None, - }, - &[], - ) - .unwrap(); - } - } - - // Update the block so that staked balances appear. - app.update_block(|block| block.height += 1); - - core_contract -} - -pub(crate) fn _instantiate_with_native_staked_balances_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, - query_auth: RawContract, - dao_code_hash: String, -) -> ContractInfo { - let proposal_module_instantiate_info = app.store_code(proposal_single_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![InitialBalance { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - // Collapse balances so that we can test double votes. - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|InitialBalance { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let native_stake_instantiate_info = app.store_code(native_staked_balances_voting_contract()); - let core_contract_instantiate_info = app.store_code(cw_core_contract()); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - dao_uri: None, - image_url: None, - automatically_add_snip20s: true, - automatically_add_snip721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: native_stake_instantiate_info.code_id, - code_hash: native_stake_instantiate_info.code_hash, - msg: to_binary(&dao_voting_token_staked::msg::InstantiateMsg { - token_info: dao_voting_token_staked::msg::TokenInfo::Existing { - denom: "ujuno".to_string(), - }, - unstaking_duration: None, - active_threshold: None, - query_auth, - dao_code_hash, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_instantiate_info.code_id, - code_hash: proposal_module_instantiate_info.code_hash, - msg: to_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), - }; - - let core_contract = app - .instantiate_contract( - core_contract_instantiate_info.clone(), - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_contract.clone().code_hash, - core_contract.clone().address.to_string(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let native_staking_contract_info = gov_state.voting_module; - - for InitialBalance { address, amount } in initial_balances { - app.sudo(SudoMsg::Bank(BankSudo::Mint { - to_address: address.clone(), - amount: vec![Coin { - denom: "ujuno".to_string(), - amount, - }], - })) - .unwrap(); - app.execute_contract( - Addr::unchecked(&address), - &ContractInfo { - address: native_staking_contract_info.clone().addr, - code_hash: native_staking_contract_info.clone().code_hash, - }, - &dao_voting_token_staked::msg::ExecuteMsg::Stake {}, - &[Coin { - amount, - denom: "ujuno".to_string(), - }], - ) - .unwrap(); - } - - app.update_block(next_block); - - core_contract -} - -pub(crate) fn _instantiate_with_staked_balances_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, - dao_contract_instantiate_info: ContractInstantiationInfo, - proposal_module_contract_info: ContractInstantiationInfo, - query_auth: RawContract, -) -> ContractInfo { - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![InitialBalance { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - // Collapse balances so that we can test double votes. - let initial_balances: Vec = { - let mut already_seen = vec![]; - initial_balances - .into_iter() - .filter(|InitialBalance { address, amount: _ }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .collect() - }; - - let snip20_contract_info = instantiate_snip20(app, initial_balances.clone()); - let snip20_stake_contract_info = instantiate_staking( - app, - snip20_contract_info.clone().address, - snip20_contract_info.clone().code_hash, - Some(Duration::Height(6)), - query_auth.clone(), - ); - let staked_balances_voting_instantiate_info = - app.store_code(snip20_staked_balances_voting_contract()); - - let instantiate_core = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - dao_uri: None, - image_url: None, - automatically_add_snip20s: true, - automatically_add_snip721s: false, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: staked_balances_voting_instantiate_info.code_id, - code_hash: staked_balances_voting_instantiate_info.code_hash, - msg: to_binary(&dao_voting_snip20_staked::msg::InstantiateMsg { - active_threshold: None, - token_info: dao_voting_snip20_staked::msg::Snip20TokenInfo::Existing { - address: snip20_contract_info.clone().address.to_string(), - code_hash: snip20_contract_info.clone().code_hash, - staking_contract: dao_voting_snip20_staked::msg::StakingInfo::Existing { - staking_contract_address: snip20_stake_contract_info - .clone() - .address - .to_string(), - staking_contract_code_hash: snip20_stake_contract_info.clone().code_hash, - }, - }, - dao_code_hash: dao_contract_instantiate_info.clone().code_hash, - query_auth, - }) - .unwrap(), - admin: None, - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_contract_info.code_id, - code_hash: proposal_module_contract_info.code_hash, - msg: to_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module.".to_string(), - }], - initial_items: None, - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), - }; - - let core_contract = app - .instantiate_contract( - dao_contract_instantiate_info, - Addr::unchecked(CREATOR_ADDR), - &instantiate_core, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - let gov_state: dao_interface::query::DumpStateResponse = app - .wrap() - .query_wasm_smart( - core_contract.clone().code_hash, - core_contract.clone().address.to_string(), - &dao_interface::msg::QueryMsg::DumpState {}, - ) - .unwrap(); - let voting_module = gov_state.voting_module; - - let staking_contract: AnyContractInfo = app - .wrap() - .query_wasm_smart( - voting_module.clone().code_hash, - voting_module.clone().addr.to_string(), - &dao_voting_snip20_staked::msg::QueryMsg::StakingContract {}, - ) - .unwrap(); - let token_contract: AnyContractInfo = app - .wrap() - .query_wasm_smart( - voting_module.clone().code_hash, - voting_module.clone().addr.to_string(), - &dao_interface::voting::Query::TokenContract {}, - ) - .unwrap(); - - // Stake all the initial balances. - for InitialBalance { address, amount } in initial_balances { - app.execute_contract( - Addr::unchecked(address), - &ContractInfo { - address: token_contract.clone().addr, - code_hash: token_contract.clone().code_hash, - }, - &snip20_reference_impl::msg::ExecuteMsg::Send { - recipient: staking_contract.clone().addr.to_string(), - recipient_code_hash: Some(staking_contract.clone().code_hash), - amount, - msg: Some(to_binary(&snip20_stake::msg::ReceiveMsg::Stake {}).unwrap()), - decoys: None, - memo: None, - entropy: None, - padding: None, - }, - &[], - ) - .unwrap(); - } - - // Update the block so that those staked balances appear. - app.update_block(|block| block.height += 1); - - core_contract -} - -pub(crate) fn _instantiate_with_staking_active_threshold( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_balances: Option>, - active_threshold: Option, - dao_code_hash: String, - query_auth: RawContract, -) -> ContractInfo { - let proposal_module_contract_instantiate_info = app.store_code(proposal_single_contract()); - let snip20_instantiate_info = app.store_code(snip20_base_contract()); - let snip20_staking_instantiate_info = app.store_code(snip20_stake_contract()); - let core_instantiate_info = app.store_code(cw_core_contract()); - let voting_instantiate_info = app.store_code(snip20_staked_balances_voting_contract()); - - let initial_balances = initial_balances.unwrap_or_else(|| { - vec![InitialBalanceSnip20Staked { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] - }); - - let governance_instantiate = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - dao_uri: None, - image_url: None, - automatically_add_snip20s: true, - automatically_add_snip721s: true, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: voting_instantiate_info.code_id, - code_hash: voting_instantiate_info.code_hash, - msg: to_binary(&dao_voting_snip20_staked::msg::InstantiateMsg { - token_info: dao_voting_snip20_staked::msg::Snip20TokenInfo::New { - code_id: snip20_instantiate_info.code_id, - code_hash: snip20_instantiate_info.code_hash, - name: "DAO".to_string(), - symbol: "DAO".to_string(), - decimals: 6, - initial_balances, - staking_code_id: snip20_staking_instantiate_info.code_id, - staking_code_hash: snip20_staking_instantiate_info.code_hash, - unstaking_duration: None, - initial_dao_balance: None, - }, - dao_code_hash, - query_auth, - active_threshold, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_contract_instantiate_info.code_id, - code_hash: proposal_module_contract_instantiate_info.code_hash, - msg: to_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module".to_string(), - }], - initial_items: None, - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), - }; - - app.instantiate_contract( - core_instantiate_info.clone(), - Addr::unchecked(CREATOR_ADDR), - &governance_instantiate, - &[], - "DAO DAO", - None, - ) - .unwrap() -} - -pub(crate) fn _instantiate_with_cw4_groups_governance( - app: &mut App, - proposal_module_instantiate: InstantiateMsg, - initial_weights: Option>, - query_auth: RawContract, - dao_code_hash: String, -) -> ContractInfo { - let proposal_module_contract_instantiate_info = app.store_code(proposal_single_contract()); - let cw4_instantiate_info = app.store_code(cw4_group_contract()); - let core_instantiate_info = app.store_code(cw_core_contract()); - let votemod_instantiate_info = app.store_code(cw4_voting_contract()); - - let initial_weights = initial_weights.unwrap_or_else(|| { - vec![InitialBalance { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(1), - }] - }); - - // Remove duplicates so that we can test duplicate voting. - let initial_weights: Vec = { - let mut already_seen = vec![]; - initial_weights - .into_iter() - .filter(|InitialBalance { address, .. }| { - if already_seen.contains(address) { - false - } else { - already_seen.push(address.clone()); - true - } - }) - .map(|InitialBalance { address, amount }| cw4::Member { - addr: address, - weight: amount.u128() as u64, - }) - .collect() - }; - - let governance_instantiate = dao_interface::msg::InstantiateMsg { - admin: None, - name: "DAO DAO".to_string(), - description: "A DAO that builds DAOs".to_string(), - dao_uri: None, - image_url: None, - automatically_add_snip20s: true, - automatically_add_snip721s: true, - voting_module_instantiate_info: ModuleInstantiateInfo { - code_id: votemod_instantiate_info.code_id, - code_hash: votemod_instantiate_info.code_hash, - msg: to_binary(&dao_voting_cw4::msg::InstantiateMsg { - group_contract: GroupContract::New { - cw4_group_code_id: cw4_instantiate_info.code_id, - cw4_group_code_hash: cw4_instantiate_info.code_hash, - initial_members: initial_weights, - }, - query_auth, - dao_code_hash, - }) - .unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO voting module".to_string(), - }, - proposal_modules_instantiate_info: vec![ModuleInstantiateInfo { - code_id: proposal_module_contract_instantiate_info.code_id, - code_hash: proposal_module_contract_instantiate_info.code_hash, - msg: to_binary(&proposal_module_instantiate).unwrap(), - admin: Some(Admin::CoreModule {}), - funds: vec![], - label: "DAO DAO governance module".to_string(), - }], - initial_items: None, - snip20_code_hash: "".to_string(), - snip721_code_hash: "".to_string(), - }; - - let core_contract = app - .instantiate_contract( - core_instantiate_info.clone(), - Addr::unchecked(CREATOR_ADDR), - &governance_instantiate, - &[], - "DAO DAO", - None, - ) - .unwrap(); - - // Update the block so that weights appear. - app.update_block(|block| block.height += 1); - - core_contract -} - pub(crate) fn instantiate_query_auth(app: &mut App) -> ContractInfo { let query_auth_info = app.store_code(query_auth_contract()); let msg = shade_protocol::contract_interfaces::query_auth::InstantiateMsg { @@ -727,63 +53,3 @@ pub(crate) fn instantiate_query_auth(app: &mut App) -> ContractInfo { ) .unwrap() } - -pub(crate) fn instantiate_staking( - app: &mut App, - snip20: Addr, - snip20_code_hash: String, - unstaking_duration: Option, - query_auth: RawContract, -) -> ContractInfo { - let staking_info = app.store_code(snip20_stake_contract()); - let msg = snip20_stake::msg::InstantiateMsg { - owner: Some(CREATOR_ADDR.to_string()), - token_address: snip20.to_string(), - unstaking_duration, - token_code_hash: Some(snip20_code_hash), - query_auth, - }; - app.instantiate_contract( - staking_info, - Addr::unchecked(CREATOR_ADDR), - &msg, - &[], - "staking", - Some("admin".to_string()), - ) - .unwrap() -} - -pub(crate) fn instantiate_snip20( - app: &mut App, - initial_balances: Vec, -) -> ContractInfo { - let snip20_info = app.store_code(snip20_base_contract()); - let msg = snip20_reference_impl::msg::InstantiateMsg { - name: String::from("Test"), - symbol: String::from("TEST"), - decimals: 6, - initial_balances: Some(initial_balances), - admin: None, - prng_seed: to_binary("seed").unwrap(), - config: Some(snip20_reference_impl::msg::InitConfig { - public_total_supply: Some(true), - enable_deposit: None, - enable_redeem: None, - enable_mint: None, - enable_burn: None, - can_modify_denoms: None, - }), - supported_denoms: None, - }; - - app.instantiate_contract( - snip20_info, - Addr::unchecked(CREATOR_ADDR), - &msg, - &[], - "snip20", - None, - ) - .unwrap() -} diff --git a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs deleted file mode 100644 index 42720bf..0000000 --- a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs +++ /dev/null @@ -1,475 +0,0 @@ -// use cosmwasm_std::{to_binary, Addr, Uint128, WasmMsg}; -// use secret_multi_test::{next_block, App, Executor}; -// use secret_utils::Duration; -// use dao_interface::query::{GetItemResponse, ProposalModuleCountResponse}; - -// use dao_voting::veto::VetoConfig; -// use dao_voting::{ -// deposit::{UncheckedDepositInfo, VotingModuleTokenType}, -// status::Status, -// }; - -// use crate::testing::contracts::{v1_dao_dao_contract, v1_proposal_single_contract}; -// use crate::testing::queries::query_list_proposals; -// use crate::testing::{ -// execute::{execute_proposal, make_proposal, vote_on_proposal}, -// instantiate::get_pre_propose_info, -// queries::{query_proposal, query_proposal_count}, -// }; - -// /// This test attempts to simulate a realistic migration from DAO DAO -// /// v1 to v2. Other tests in `/tests/tests.rs` check that versions and -// /// top-level configs are updated correctly during migration. This -// /// concerns itself more with more subtle state in the contracts that -// /// is less functionality critical and thus more likely to be -// /// overlooked in migration logic. -// /// -// /// - I can migrate with tokens in the treasury and completed -// /// proposals. -// /// -// /// - I can migrate an open and unexecutable proposal, and use -// /// `close_proposal_on_execution_failure` to close it once the -// /// migration completes. -// /// -// /// - Proposal count remains accurate after proposal migration. -// /// -// /// - Items are not overriden during migration. -// #[test] -// fn test_v1_v2_full_migration() { -// let sender = Addr::unchecked("sender"); - -// let mut app = App::default(); - -// // ---- -// // instantiate a v1 DAO -// // ---- - -// let proposal_code = app.store_code(v1_proposal_single_contract()); -// let core_code = app.store_code(v1_dao_dao_contract()); - -// // cw20 staking and voting module has not changed across v1->v2 so -// // we use the current edition. -// let cw20_code = app.store_code(cw20_base_contract()); -// let cw20_stake_code = app.store_code(cw20_stake_contract()); -// let voting_code = app.store_code(cw20_staked_balances_voting_contract()); - -// let initial_balances = vec![Cw20Coin { -// address: sender.to_string(), -// amount: Uint128::new(2), -// }]; - -// let core = app -// .instantiate_contract( -// core_code, -// sender.clone(), -// &cw_core_v1::msg::InstantiateMsg { -// admin: Some(sender.to_string()), -// name: "n".to_string(), -// description: "d".to_string(), -// image_url: Some("i".to_string()), -// automatically_add_cw20s: false, -// automatically_add_cw721s: true, -// voting_module_instantiate_info: cw_core_v1::msg::ModuleInstantiateInfo { -// code_id: voting_code, -// msg: to_binary(&dao_voting_cw20_staked::msg::InstantiateMsg { -// active_threshold: None, -// token_info: dao_voting_cw20_staked::msg::TokenInfo::New { -// code_id: cw20_code, -// label: "token".to_string(), -// name: "name".to_string(), -// symbol: "symbol".to_string(), -// decimals: 6, -// initial_balances, -// marketing: None, -// staking_code_id: cw20_stake_code, -// unstaking_duration: None, -// initial_dao_balance: Some(Uint128::new(100)), -// }, -// }) -// .unwrap(), -// admin: cw_core_v1::msg::Admin::CoreContract {}, -// label: "voting".to_string(), -// }, -// proposal_modules_instantiate_info: vec![cw_core_v1::msg::ModuleInstantiateInfo { -// code_id: proposal_code, -// msg: to_binary(&cw_proposal_single_v1::msg::InstantiateMsg { -// threshold: voting_v1::Threshold::AbsolutePercentage { -// percentage: voting_v1::PercentageThreshold::Majority {}, -// }, -// max_voting_period: cw_utils_v1::Duration::Height(6), -// min_voting_period: None, -// only_members_execute: false, -// allow_revoting: false, -// deposit_info: None, -// }) -// .unwrap(), -// admin: cw_core_v1::msg::Admin::CoreContract {}, -// label: "proposal".to_string(), -// }], -// initial_items: Some(vec![cw_core_v1::msg::InitialItem { -// key: "key".to_string(), -// value: "value".to_string(), -// }]), -// }, -// &[], -// "core", -// Some(sender.to_string()), -// ) -// .unwrap(); -// app.execute( -// sender.clone(), -// WasmMsg::UpdateAdmin { -// contract_addr: core.to_string(), -// admin: core.to_string(), -// } -// .into(), -// ) -// .unwrap(); - -// // ---- -// // stake tokens in the DAO -// // ---- - -// let token = { -// let voting: Addr = app -// .wrap() -// .query_wasm_smart(&core, &cw_core_v1::msg::QueryMsg::VotingModule {}) -// .unwrap(); -// let staking: Addr = app -// .wrap() -// .query_wasm_smart( -// &voting, -// &dao_voting_cw20_staked::msg::QueryMsg::StakingContract {}, -// ) -// .unwrap(); -// let token: Addr = app -// .wrap() -// .query_wasm_smart( -// &voting, -// &dao_voting_cw20_staked::msg::QueryMsg::TokenContract {}, -// ) -// .unwrap(); -// app.execute_contract( -// sender.clone(), -// token.clone(), -// &cw20::Cw20ExecuteMsg::Send { -// contract: staking.into_string(), -// amount: Uint128::new(1), -// msg: to_binary(&cw20_stake::msg::ReceiveMsg::Stake {}).unwrap(), -// }, -// &[], -// ) -// .unwrap(); -// app.update_block(next_block); -// token -// }; - -// // ---- -// // create a proposal and add tokens to the treasury. -// // ---- - -// let proposal = { -// let modules: Vec = app -// .wrap() -// .query_wasm_smart( -// &core, -// &cw_core_v1::msg::QueryMsg::ProposalModules { -// start_at: None, -// limit: None, -// }, -// ) -// .unwrap(); -// assert!(modules.len() == 1); -// modules.into_iter().next().unwrap() -// }; - -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Propose { -// title: "t".to_string(), -// description: "d".to_string(), -// msgs: vec![WasmMsg::Execute { -// contract_addr: core.to_string(), -// msg: to_binary(&cw_core_v1::msg::ExecuteMsg::UpdateCw20List { -// to_add: vec![token.to_string()], -// to_remove: vec![], -// }) -// .unwrap(), -// funds: vec![], -// } -// .into()], -// }, -// &[], -// ) -// .unwrap(); -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Vote { -// proposal_id: 1, -// vote: voting_v1::Vote::Yes, -// }, -// &[], -// ) -// .unwrap(); -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 1 }, -// &[], -// ) -// .unwrap(); -// let tokens: Vec = app -// .wrap() -// .query_wasm_smart( -// &core, -// &cw_core_v1::msg::QueryMsg::Cw20Balances { -// start_at: None, -// limit: None, -// }, -// ) -// .unwrap(); -// assert_eq!( -// tokens, -// vec![cw_core_v1::query::Cw20BalanceResponse { -// addr: token.clone(), -// balance: Uint128::new(100), -// }] -// ); - -// // ---- -// // Create a proposal that is unexecutable without close_proposal_on_execution_failure -// // ---- - -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Propose { -// title: "t".to_string(), -// description: "d".to_string(), -// msgs: vec![WasmMsg::Execute { -// contract_addr: token.to_string(), -// msg: to_binary(&cw20::Cw20ExecuteMsg::Transfer { -// recipient: sender.to_string(), -// // more tokens than the DAO posseses. -// amount: Uint128::new(101), -// }) -// .unwrap(), -// funds: vec![], -// } -// .into()], -// }, -// &[], -// ) -// .unwrap(); -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Vote { -// proposal_id: 2, -// vote: voting_v1::Vote::Yes, -// }, -// &[], -// ) -// .unwrap(); -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 2 }, -// &[], -// ) -// // can not be executed. -// .unwrap_err(); -// let cw_proposal_single_v1::query::ProposalResponse { -// proposal: cw_proposal_single_v1::proposal::Proposal { status, .. }, -// .. -// } = app -// .wrap() -// .query_wasm_smart( -// &proposal, -// &cw_proposal_single_v1::msg::QueryMsg::Proposal { proposal_id: 2 }, -// ) -// .unwrap(); -// assert_eq!(status, voting_v1::Status::Passed); - -// // ---- -// // create a proposal to migrate to v2 -// // ---- - -// let v2_core_code = app.store_code(dao_dao_contract()); -// let v2_proposal_code = app.store_code(proposal_single_contract()); - -// let pre_propose_info = get_pre_propose_info( -// &mut app, -// Some(UncheckedDepositInfo { -// denom: dao_voting::deposit::DepositToken::VotingModuleToken { -// token_type: VotingModuleTokenType::Cw20, -// }, -// amount: Uint128::new(1), -// refund_policy: dao_voting::deposit::DepositRefundPolicy::OnlyPassed, -// }), -// false, -// ); - -// // now migrate with valid config -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Propose { -// title: "t".to_string(), -// description: "d".to_string(), -// msgs: vec![ -// WasmMsg::Migrate { -// contract_addr: core.to_string(), -// new_code_id: v2_core_code, -// msg: to_binary(&dao_interface::msg::MigrateMsg::FromV1 { -// dao_uri: Some("dao-uri".to_string()), -// params: None, -// }) -// .unwrap(), -// } -// .into(), -// WasmMsg::Migrate { -// contract_addr: proposal.to_string(), -// new_code_id: v2_proposal_code, -// msg: to_binary(&crate::msg::MigrateMsg::FromV1 { -// close_proposal_on_execution_failure: true, -// pre_propose_info, -// veto: Some(VetoConfig { -// timelock_duration: Duration::Height(10), -// vetoer: sender.to_string(), -// early_execute: true, -// veto_before_passed: false, -// }), -// }) -// .unwrap(), -// } -// .into(), -// ], -// }, -// &[], -// ) -// .unwrap(); -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Vote { -// proposal_id: 3, -// vote: voting_v1::Vote::Yes, -// }, -// &[], -// ) -// .unwrap(); -// app.execute_contract( -// sender.clone(), -// proposal.clone(), -// &cw_proposal_single_v1::msg::ExecuteMsg::Execute { proposal_id: 3 }, -// &[], -// ) -// .unwrap(); - -// // ---- -// // execute proposal two. the addition of -// // close_proposal_on_execution_failure ought to allow it to close. -// // ---- -// execute_proposal(&mut app, &proposal, sender.as_str(), 2); -// let status = query_proposal(&app, &proposal, 2).proposal.status; -// assert_eq!(status, Status::ExecutionFailed); - -// // ---- -// // check that proposal count is still three after proposal state migration. -// // ---- -// let count = query_proposal_count(&app, &proposal); -// assert_eq!(count, 3); - -// let migrated_existing_props = query_list_proposals(&app, &proposal, None, None); -// // assert that even though we migrate with a veto config, -// // existing proposals are not affected -// for prop in migrated_existing_props.proposals { -// assert_eq!(prop.proposal.veto, None); -// } -// // ---- -// // check that proposal module counts have been updated. -// // ---- -// let module_counts: ProposalModuleCountResponse = app -// .wrap() -// .query_wasm_smart(&core, &dao_interface::msg::QueryMsg::ProposalModuleCount {}) -// .unwrap(); -// assert_eq!( -// module_counts, -// ProposalModuleCountResponse { -// active_proposal_module_count: 1, -// total_proposal_module_count: 1, -// } -// ); - -// // ---- -// // check that items are not overriden in migration. -// // ---- -// let item: GetItemResponse = app -// .wrap() -// .query_wasm_smart( -// &core, -// &dao_interface::msg::QueryMsg::GetItem { -// key: "key".to_string(), -// }, -// ) -// .unwrap(); -// assert_eq!( -// item, -// GetItemResponse { -// item: Some("value".to_string()) -// } -// ); - -// // ---- -// // check that proposal can still be created an executed. -// // ---- -// make_proposal( -// &mut app, -// &proposal, -// sender.as_str(), -// vec![WasmMsg::Execute { -// contract_addr: core.to_string(), -// msg: to_binary(&dao_interface::msg::ExecuteMsg::UpdateCw20List { -// to_add: vec![], -// to_remove: vec![token.into_string()], -// }) -// .unwrap(), -// funds: vec![], -// } -// .into()], -// ); -// vote_on_proposal( -// &mut app, -// &proposal, -// sender.as_str(), -// 4, -// dao_voting::voting::Vote::Yes, -// ); - -// let new_prop = query_proposal(&app, &proposal, 4); -// assert_eq!( -// new_prop.proposal.veto, -// Some(VetoConfig { -// timelock_duration: Duration::Height(10), -// vetoer: sender.to_string(), -// early_execute: true, -// veto_before_passed: false, -// }) -// ); - -// execute_proposal(&mut app, &proposal, sender.as_str(), 4); -// let tokens: Vec = app -// .wrap() -// .query_wasm_smart( -// &core, -// &dao_interface::msg::QueryMsg::Cw20Balances { -// start_after: None, -// limit: None, -// }, -// ) -// .unwrap(); -// assert!(tokens.is_empty()) -// } diff --git a/contracts/proposal/dao-proposal-single/src/testing/mod.rs b/contracts/proposal/dao-proposal-single/src/testing/mod.rs index 9fae24e..78a717a 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/mod.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/mod.rs @@ -1,9 +1,6 @@ -mod adversarial_tests; mod contracts; -mod do_votes; mod execute; mod instantiate; -mod migration_tests; mod queries; mod tests;