diff --git a/contracts/proposal/dao-proposal-multiple/src/contract.rs b/contracts/proposal/dao-proposal-multiple/src/contract.rs index da1d44465..abe9e73a4 100644 --- a/contracts/proposal/dao-proposal-multiple/src/contract.rs +++ b/contracts/proposal/dao-proposal-multiple/src/contract.rs @@ -101,7 +101,7 @@ pub fn execute( proposal_id, vote, rationale, - } => execute_vote(deps, env, info, proposal_id, vote, rationale), + } => execute_vote(deps, env, info.sender, proposal_id, vote, rationale), ExecuteMsg::Execute { proposal_id } => execute_execute(deps, env, info, proposal_id), ExecuteMsg::Veto { proposal_id } => execute_veto(deps, env, info, proposal_id), ExecuteMsg::Close { proposal_id } => execute_close(deps, env, info, proposal_id), @@ -255,7 +255,14 @@ pub fn execute_propose( // Auto cast vote if given. let (vote_hooks, vote_attributes) = if let Some(vote) = vote { - let response = execute_vote(deps, env, info, id, vote.vote, vote.rationale.clone())?; + let response = execute_vote( + deps, + env, + proposer.clone(), + id, + vote.vote, + vote.rationale.clone(), + )?; ( response.messages, vec![ @@ -359,7 +366,7 @@ pub fn execute_veto( pub fn execute_vote( deps: DepsMut, env: Env, - info: MessageInfo, + sender: Addr, proposal_id: u64, vote: MultipleChoiceVote, rationale: Option, @@ -387,7 +394,7 @@ pub fn execute_vote( let vote_power = get_voting_power( deps.as_ref(), - info.sender.clone(), + sender.clone(), &config.dao, Some(prop.start_height), )?; @@ -395,7 +402,7 @@ pub fn execute_vote( return Err(ContractError::NotRegistered {}); } - BALLOTS.update(deps.storage, (proposal_id, &info.sender), |bal| match bal { + BALLOTS.update(deps.storage, (proposal_id, &sender), |bal| match bal { Some(current_ballot) => { if prop.allow_revoting { if current_ballot.vote == vote { @@ -441,14 +448,14 @@ pub fn execute_vote( VOTE_HOOKS, deps.storage, proposal_id, - info.sender.to_string(), + sender.to_string(), vote.to_string(), )?; Ok(Response::default() .add_submessages(change_hooks) .add_submessages(vote_hooks) .add_attribute("action", "vote") - .add_attribute("sender", info.sender) + .add_attribute("sender", sender) .add_attribute("proposal_id", proposal_id.to_string()) .add_attribute("position", vote.to_string()) .add_attribute( diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs index d85499917..8f3b593ea 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/adversarial_tests.rs @@ -53,7 +53,7 @@ fn setup_test(_messages: Vec) -> CommonTest { let mc_options = MultipleChoiceOptions { options }; - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, mc_options); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, mc_options, None); CommonTest { app, @@ -332,7 +332,7 @@ pub fn test_allow_voting_after_proposal_execution_pre_expiration_cw20() { let mc_options = MultipleChoiceOptions { options }; - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, mc_options); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, mc_options, None); // assert initial CREATOR_ADDR address balance is 0 let balance = query_balance_cw20(&app, gov_token.to_string(), CREATOR_ADDR); diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs b/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs index 07c6cb682..3e136a786 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/execute.rs @@ -4,8 +4,10 @@ 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, proposal::MultipleChoiceProposeMsg as ProposeMsg, + deposit::CheckedDepositInfo, + multiple_choice::{MultipleChoiceAutoVote, MultipleChoiceOptions}, + pre_propose::ProposalCreationPolicy, + proposal::MultipleChoiceProposeMsg as ProposeMsg, }; use crate::{ @@ -24,6 +26,7 @@ pub fn make_proposal( proposal_multiple: &Addr, proposer: &str, choices: MultipleChoiceOptions, + vote: Option, ) -> u64 { let proposal_creation_policy = query_creation_policy(app, proposal_multiple); @@ -73,7 +76,7 @@ pub fn make_proposal( description: "description".to_string(), choices, proposer: None, - vote: None, + vote, }), &[], ) @@ -87,7 +90,7 @@ pub fn make_proposal( title: "title".to_string(), description: "description".to_string(), choices, - vote: None, + vote, }, }, &funds, diff --git a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs index 5d4b09fe0..d7b51beaa 100644 --- a/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-multiple/src/testing/tests.rs @@ -8,6 +8,7 @@ use cw_multi_test::{next_block, App, BankSudo, Contract, ContractWrapper, Execut use cw_utils::Duration; use dao_interface::state::ProposalModule; use dao_interface::state::{Admin, ModuleInstantiateInfo}; +use dao_voting::multiple_choice::MultipleChoiceAutoVote; use dao_voting::veto::{VetoConfig, VetoError}; use dao_voting::{ deposit::{ @@ -156,7 +157,7 @@ fn test_propose() { title: "title".to_string(), }, MultipleChoiceOption { - description: "multiple choice option 1".to_string(), + description: "multiple choice option 2".to_string(), msgs: vec![], title: "title".to_string(), }, @@ -165,7 +166,7 @@ fn test_propose() { let mc_options = MultipleChoiceOptions { options }; // Create a new proposal. - make_proposal(&mut app, &govmod, CREATOR_ADDR, mc_options.clone()); + make_proposal(&mut app, &govmod, CREATOR_ADDR, mc_options.clone(), None); let created: ProposalResponse = query_proposal(&app, &govmod, 1); @@ -312,6 +313,255 @@ fn test_proposal_count_initialized_to_zero() { assert_eq!(proposal_count, 0); } +#[test] +fn test_propose_auto_vote_winner() { + 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 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + + let mc_options = MultipleChoiceOptions { options }; + + // Create a new proposal and auto-vote on the first option. + make_proposal( + &mut app, + &govmod, + CREATOR_ADDR, + mc_options.clone(), + Some(MultipleChoiceAutoVote { + vote: MultipleChoiceVote { option_id: 0 }, + rationale: Some("rationale".to_string()), + }), + ); + + 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::Passed, + voting_strategy, + total_power: Uint128::new(100_000_000), + votes: MultipleChoiceVotes { + vote_weights: vec![Uint128::new(100_000_000), Uint128::zero(), Uint128::zero()], + }, + allow_revoting: false, + min_voting_period: None, + veto: None, + }; + + assert_eq!(created.proposal, expected); + assert_eq!(created.id, 1u64); +} + +#[test] +fn test_propose_auto_vote_reject() { + 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 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + + let mc_options = MultipleChoiceOptions { options }; + + // Create a new proposal and auto-vote on the first option. + make_proposal( + &mut app, + &govmod, + CREATOR_ADDR, + mc_options.clone(), + Some(MultipleChoiceAutoVote { + vote: MultipleChoiceVote { option_id: 2 }, + rationale: Some("rationale".to_string()), + }), + ); + + 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::Rejected, + voting_strategy, + total_power: Uint128::new(100_000_000), + votes: MultipleChoiceVotes { + vote_weights: vec![Uint128::zero(), Uint128::zero(), Uint128::new(100_000_000)], + }, + allow_revoting: false, + min_voting_period: None, + veto: None, + }; + + assert_eq!(created.proposal, expected); + assert_eq!(created.id, 1u64); +} + +#[test] +#[should_panic(expected = "Not registered to vote (no voting power) at time of proposal creation")] +fn test_propose_non_member_auto_vote_fail() { + 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 2".to_string(), + msgs: vec![], + title: "title".to_string(), + }, + ]; + + let mc_options = MultipleChoiceOptions { options }; + + // Should fail if non-member tries to vote on proposal creation. + make_proposal( + &mut app, + &govmod, + "anyone", + mc_options.clone(), + Some(MultipleChoiceAutoVote { + vote: MultipleChoiceVote { option_id: 0 }, + rationale: Some("rationale".to_string()), + }), + ); +} + #[test] fn test_no_early_pass_with_min_duration() { let mut app = App::default(); @@ -975,7 +1225,7 @@ fn test_take_proposal_deposit() { ) .unwrap(); - make_proposal(&mut app, &govmod, "blue", mc_options); + make_proposal(&mut app, &govmod, "blue", mc_options, None); // Proposal has been executed so deposit has been refunded. let balance = query_balance_cw20(&app, token, "blue".to_string()); @@ -1070,7 +1320,7 @@ fn test_take_native_proposal_deposit() { ) .unwrap_err(); - make_proposal(&mut app, &govmod, "blue", mc_options); + make_proposal(&mut app, &govmod, "blue", mc_options, None); // Proposal has been executed so deposit has been refunded. let balance = query_balance_native(&app, "blue", denom); @@ -1179,7 +1429,7 @@ fn test_native_proposal_deposit() { .unwrap(); // Adding deposit will work - make_proposal(&mut app, &govmod, "blue", mc_options); + make_proposal(&mut app, &govmod, "blue", mc_options, None); // "blue" has been refunded let balance = query_balance_native(&app, "blue", "ujuno"); @@ -1940,6 +2190,7 @@ fn test_open_proposal_submission() { }, ], }, + None, ); let created: ProposalResponse = query_proposal(&app, &govmod, 1); @@ -4101,6 +4352,7 @@ fn test_no_double_refund_on_execute_fail_and_close() { &govmod, Addr::unchecked(CREATOR_ADDR).as_str(), choices, + None, ); // Vote on proposal diff --git a/contracts/proposal/dao-proposal-single/src/contract.rs b/contracts/proposal/dao-proposal-single/src/contract.rs index d26c00752..916ac18ed 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -104,7 +104,7 @@ pub fn execute( proposal_id, vote, rationale, - } => execute_vote(deps, env, info, proposal_id, vote, rationale), + } => execute_vote(deps, env, info.sender, proposal_id, vote, rationale), ExecuteMsg::UpdateRationale { proposal_id, rationale, @@ -256,7 +256,7 @@ pub fn execute_propose( // Auto cast vote if given. let (vote_hooks, vote_attributes) = if let Some(vote) = vote { - let response = execute_vote(deps, env, info, id, vote.vote, vote.rationale.clone())?; + let response = execute_vote(deps, env, proposer, id, vote.vote, vote.rationale.clone())?; ( response.messages, vec![ @@ -474,7 +474,7 @@ pub fn execute_execute( pub fn execute_vote( deps: DepsMut, env: Env, - info: MessageInfo, + sender: Addr, proposal_id: u64, vote: Vote, rationale: Option, @@ -497,7 +497,7 @@ pub fn execute_vote( let vote_power = get_voting_power( deps.as_ref(), - info.sender.clone(), + sender.clone(), &config.dao, Some(prop.start_height), )?; @@ -505,7 +505,7 @@ pub fn execute_vote( return Err(ContractError::NotRegistered {}); } - BALLOTS.update(deps.storage, (proposal_id, &info.sender), |bal| match bal { + BALLOTS.update(deps.storage, (proposal_id, &sender), |bal| match bal { Some(current_ballot) => { if prop.allow_revoting { if current_ballot.vote == vote { @@ -557,7 +557,7 @@ pub fn execute_vote( VOTE_HOOKS, deps.storage, proposal_id, - info.sender.to_string(), + sender.to_string(), vote.to_string(), )?; @@ -565,7 +565,7 @@ pub fn execute_vote( .add_submessages(change_hooks) .add_submessages(vote_hooks) .add_attribute("action", "vote") - .add_attribute("sender", info.sender) + .add_attribute("sender", sender) .add_attribute("proposal_id", proposal_id.to_string()) .add_attribute("position", vote.to_string()) .add_attribute( diff --git a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs index b8883e933..beed03604 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs @@ -39,7 +39,7 @@ fn setup_test(messages: Vec) -> CommonTest { // 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); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, messages, None); CommonTest { app, @@ -204,7 +204,7 @@ pub fn test_executed_prop_state_remains_after_vote_swing() { 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![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); // someone quickly votes, proposal gets executed vote_on_proposal( @@ -320,6 +320,7 @@ pub fn test_passed_prop_state_remains_after_vote_swing() { funds: vec![], } .into()], + None, ); // assert that the initial "threshold" address balance is 0 diff --git a/contracts/proposal/dao-proposal-single/src/testing/execute.rs b/contracts/proposal/dao-proposal-single/src/testing/execute.rs index 8d2ad5917..c8511271f 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/execute.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/execute.rs @@ -31,6 +31,7 @@ pub(crate) fn make_proposal( proposal_single: &Addr, proposer: &str, msgs: Vec, + vote: Option, ) -> u64 { let proposal_creation_policy = query_creation_policy(app, proposal_single); @@ -80,7 +81,7 @@ pub(crate) fn make_proposal( description: "description".to_string(), msgs: msgs.clone(), proposer: None, - vote: None, + vote, }), &[], ) @@ -94,7 +95,7 @@ pub(crate) fn make_proposal( title: "title".to_string(), description: "description".to_string(), msgs: msgs.clone(), - vote: None, + vote, }, }, &funds, diff --git a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs index 8dab11460..83196d8cf 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/migration_tests.rs @@ -443,6 +443,7 @@ fn test_v1_v2_full_migration() { funds: vec![], } .into()], + None, ); vote_on_proposal( &mut app, diff --git a/contracts/proposal/dao-proposal-single/src/testing/tests.rs b/contracts/proposal/dao-proposal-single/src/testing/tests.rs index 7e25a0dfc..4246e5a09 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/tests.rs @@ -28,7 +28,7 @@ use dao_voting::{ status::Status, threshold::{ActiveThreshold, PercentageThreshold, Threshold}, veto::{VetoConfig, VetoError}, - voting::{Vote, Votes}, + voting::{SingleChoiceAutoVote, Vote, Votes}, }; use crate::{ @@ -88,7 +88,7 @@ fn setup_test(messages: Vec) -> CommonTest { // 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); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, messages, None); CommonTest { app, @@ -157,7 +157,7 @@ fn test_simple_proposal_cw4_voting() { let instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); let core_addr = instantiate_with_cw4_groups_governance(&mut app, instantiate, None); let proposal_module = query_single_proposal_module(&app, &core_addr); - let id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + let id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); let created = query_proposal(&app, &proposal_module, id); let current_block = app.block_info(); @@ -194,6 +194,104 @@ fn test_simple_proposal_cw4_voting() { assert_eq!(deposit_response.deposit_info, None,); } +#[test] +fn test_simple_proposal_auto_vote_yes() { + let mut app = App::default(); + let instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); + let core_addr = instantiate_with_cw4_groups_governance(&mut app, instantiate, None); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![], + Some(SingleChoiceAutoVote { + vote: Vote::Yes, + rationale: Some("rationale".to_string()), + }), + ); + + let created = query_proposal(&app, &proposal_module, id); + let current_block = app.block_info(); + + // These values just come from the default instantiate message + // values. + let expected = SingleChoiceProposal { + title: "title".to_string(), + description: "description".to_string(), + proposer: Addr::unchecked(CREATOR_ADDR), + start_height: current_block.height, + expiration: Duration::Time(604800).after(¤t_block), + min_voting_period: None, + threshold: Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(15)), + quorum: PercentageThreshold::Majority {}, + }, + allow_revoting: false, + total_power: Uint128::new(1), + msgs: vec![], + status: Status::Passed, + veto: None, + votes: Votes { + yes: Uint128::new(1), + no: Uint128::zero(), + abstain: Uint128::zero(), + }, + }; + + assert_eq!(created.proposal, expected); + assert_eq!(created.id, 1u64); +} + +#[test] +fn test_simple_proposal_auto_vote_no() { + let mut app = App::default(); + let instantiate = get_default_non_token_dao_proposal_module_instantiate(&mut app); + let core_addr = instantiate_with_cw4_groups_governance(&mut app, instantiate, None); + let proposal_module = query_single_proposal_module(&app, &core_addr); + let id = make_proposal( + &mut app, + &proposal_module, + CREATOR_ADDR, + vec![], + Some(SingleChoiceAutoVote { + vote: Vote::No, + rationale: Some("rationale".to_string()), + }), + ); + + let created = query_proposal(&app, &proposal_module, id); + let current_block = app.block_info(); + + // These values just come from the default instantiate message + // values. + let expected = SingleChoiceProposal { + title: "title".to_string(), + description: "description".to_string(), + proposer: Addr::unchecked(CREATOR_ADDR), + start_height: current_block.height, + expiration: Duration::Time(604800).after(¤t_block), + min_voting_period: None, + threshold: Threshold::ThresholdQuorum { + threshold: PercentageThreshold::Percent(Decimal::percent(15)), + quorum: PercentageThreshold::Majority {}, + }, + allow_revoting: false, + total_power: Uint128::new(1), + msgs: vec![], + status: Status::Rejected, + veto: None, + votes: Votes { + yes: Uint128::zero(), + no: Uint128::new(1), + abstain: Uint128::zero(), + }, + }; + + assert_eq!(created.proposal, expected); + assert_eq!(created.id, 1u64); +} + #[test] fn test_propose_supports_stargate_messages() { // If we can make a proposal with a stargate message, we support @@ -262,7 +360,7 @@ fn test_instantiate_with_non_voting_module_cw20_deposit() { let core_addr = instantiate_with_cw4_groups_governance(&mut app, instantiate, None); let proposal_module = query_single_proposal_module(&app, &core_addr); - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); let created = query_proposal(&app, &proposal_module, proposal_id); let current_block = app.block_info(); @@ -337,6 +435,7 @@ fn test_proposal_message_execution() { } .into(), ], + None, ); let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); @@ -432,6 +531,7 @@ fn test_proposal_message_timelock_execution() -> anyhow::Result<()> { } .into(), ], + None, ); let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); @@ -547,6 +647,7 @@ fn test_open_proposal_veto_unauthorized() { } .into(), ], + None, ); // only the vetoer can veto @@ -609,6 +710,7 @@ fn test_open_proposal_veto_with_early_veto_flag_disabled() { } .into(), ], + None, ); let err: ContractError = app @@ -666,6 +768,7 @@ fn test_open_proposal_veto_with_no_timelock() { } .into(), ], + None, ); let err: ContractError = app @@ -731,6 +834,7 @@ fn test_vetoed_proposal_veto() { } .into(), ], + None, ); app.execute_contract( @@ -808,6 +912,7 @@ fn test_open_proposal_veto_early() { } .into(), ], + None, ); app.execute_contract( @@ -874,6 +979,7 @@ fn test_timelocked_proposal_veto_unauthorized() -> anyhow::Result<()> { } .into(), ], + None, ); vote_on_proposal( @@ -974,6 +1080,7 @@ fn test_timelocked_proposal_veto_expired_timelock() -> anyhow::Result<()> { } .into(), ], + None, ); vote_on_proposal( @@ -1059,6 +1166,7 @@ fn test_timelocked_proposal_execute_no_early_exec() -> anyhow::Result<()> { } .into(), ], + None, ); vote_on_proposal( @@ -1142,6 +1250,7 @@ fn test_timelocked_proposal_execute_early() -> anyhow::Result<()> { } .into(), ], + None, ); vote_on_proposal( @@ -1231,6 +1340,7 @@ fn test_timelocked_proposal_execute_active_timelock_unauthorized() -> anyhow::Re } .into(), ], + None, ); vote_on_proposal( @@ -1321,6 +1431,7 @@ fn test_timelocked_proposal_execute_expired_timelock_not_vetoer() -> anyhow::Res } .into(), ], + None, ); vote_on_proposal( @@ -1406,6 +1517,7 @@ fn test_proposal_message_timelock_veto() -> anyhow::Result<()> { } .into(), ], + None, ); let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); @@ -1530,6 +1642,7 @@ fn test_proposal_message_timelock_early_execution() -> anyhow::Result<()> { } .into(), ], + None, ); let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); @@ -1616,6 +1729,7 @@ fn test_proposal_message_timelock_veto_before_passed() { } .into(), ], + None, ); let proposal = query_proposal(&app, &proposal_module, proposal_id); @@ -1688,6 +1802,7 @@ fn test_veto_only_members_execute_proposal() -> anyhow::Result<()> { } .into(), ], + None, ); let cw20_balance = query_balance_cw20(&app, &gov_token, CREATOR_ADDR); let native_balance = query_balance_native(&app, CREATOR_ADDR, "ujuno"); @@ -1801,6 +1916,7 @@ fn test_proposal_cant_close_after_expiry_is_passed() { amount: coins(10, "ujuno"), } .into()], + None, ); vote_on_proposal(&mut app, &proposal_module, "quorum", proposal_id, Vote::Yes); let proposal = query_proposal(&app, &proposal_module, proposal_id); @@ -1849,7 +1965,7 @@ fn test_execute_no_non_passed_execution() { assert!(matches!(err, ContractError::NotPassed {})); 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![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); vote_on_proposal( &mut app, &proposal_module, @@ -1962,6 +2078,7 @@ fn test_update_config() { funds: vec![], } .into()], + None, ); vote_on_proposal( &mut app, @@ -2061,7 +2178,7 @@ fn test_anyone_may_propose_and_proposal_listing() { for addr in 'm'..'z' { let addr = addr.to_string().repeat(6); - let proposal_id = make_proposal(&mut app, &proposal_module, &addr, vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, &addr, vec![], None); vote_on_proposal( &mut app, &proposal_module, @@ -2133,6 +2250,28 @@ fn test_anyone_may_propose_and_proposal_listing() { ) } +#[test] +#[should_panic(expected = "not registered to vote (no voting power) at time of proposal creation")] +fn test_propose_non_member_auto_vote_fails() { + let mut app = App::default(); + let mut instantiate = get_default_token_dao_proposal_module_instantiate(&mut app); + instantiate.pre_propose_info = PreProposeInfo::AnyoneMayPropose {}; + let core_addr = instantiate_with_staked_balances_governance(&mut app, instantiate, None); + let proposal_module = query_single_proposal_module(&app, &core_addr); + + // Should fail if non-member tries to vote on proposal creation. + make_proposal( + &mut app, + &proposal_module, + "anyone", + vec![], + Some(SingleChoiceAutoVote { + vote: Vote::Yes, + rationale: Some("rationale".to_string()), + }), + ); +} + #[test] fn test_proposal_hook_registration() { let CommonTest { @@ -2308,7 +2447,7 @@ fn test_active_threshold_absolute() { // Proposal creation now works as tokens have been staked to reach // active threshold. - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); // Unstake some tokens to make it inactive again. let msg = cw20_stake::msg::ExecuteMsg::Unstake { @@ -2391,7 +2530,7 @@ fn test_active_threshold_percent() { // Proposal creation now works as tokens have been staked to reach // active threshold. - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); // Unstake some tokens to make it inactive again. let msg = cw20_stake::msg::ExecuteMsg::Unstake { @@ -2452,7 +2591,7 @@ fn test_min_voting_period_no_early_pass() { let proposal_module = query_single_proposal_module(&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![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); vote_on_proposal( &mut app, &proposal_module, @@ -2494,7 +2633,7 @@ fn test_min_duration_same_as_proposal_duration() { let proposal_module = query_single_proposal_module(&app, &core_addr); mint_cw20s(&mut app, &gov_token, &core_addr, "ekez", 10_000_000); - let proposal_id = make_proposal(&mut app, &proposal_module, "ekez", vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, "ekez", vec![], None); // Whale votes yes. Normally the proposal would just pass and ekez // would be out of luck. @@ -2516,7 +2655,7 @@ fn test_revoting_playthrough() { let proposal_module = query_single_proposal_module(&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![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); // Vote and change our minds a couple times. vote_on_proposal( @@ -2591,7 +2730,7 @@ fn test_allow_revoting_config_changes() { mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); // This proposal should have revoting enable for its entire // lifetime. - let revoting_proposal = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + let revoting_proposal = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); // Update the config of the proposal module to disable revoting. app.execute_contract( @@ -2616,7 +2755,8 @@ fn test_allow_revoting_config_changes() { .unwrap(); mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - let no_revoting_proposal = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + let no_revoting_proposal = + make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); vote_on_proposal( &mut app, @@ -2701,7 +2841,7 @@ fn test_three_of_five_multisig() { .unwrap() .address; - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::Yes); vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::Yes); @@ -2721,7 +2861,7 @@ fn test_three_of_five_multisig() { assert_eq!(proposal.proposal.status, Status::Executed); // Make another proposal which we'll reject. - let proposal_id = make_proposal(&mut app, &proposal_module, "one", vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, "one", vec![], None); vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::Yes); vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::No); @@ -2783,7 +2923,7 @@ fn test_three_of_five_multisig_revoting() { .unwrap() .address; - let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); vote_on_proposal(&mut app, &proposal_module, "one", proposal_id, Vote::Yes); vote_on_proposal(&mut app, &proposal_module, "two", proposal_id, Vote::Yes); @@ -3240,7 +3380,7 @@ pub fn test_migrate_updates_version() { // // Make sure we can still make a proposal and vote on it. // mint_cw20s(&mut app, &token_contract, &core_addr, CREATOR_ADDR, 1); -// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); +// let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); // vote_on_proposal( // &mut app, // &proposal_module, @@ -3303,6 +3443,7 @@ fn test_execution_failed() { amount: coins(10, "ujuno"), } .into()], + None, ); let config = query_proposal_config(&app, &proposal_module); @@ -3544,7 +3685,7 @@ fn test_proposal_creation_permissions() { assert!(matches!(err, ContractError::InvalidProposer {})); // Works normally. - let proposal_id = make_proposal(&mut app, &proposal_module, "ekez", vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, "ekez", vec![], None); let proposal = query_proposal(&app, &proposal_module, proposal_id); assert_eq!(proposal.proposal.proposer, Addr::unchecked("ekez")); vote_on_proposal( @@ -3700,7 +3841,7 @@ fn test_query_list_votes() { ]), ); let proposal_module = query_single_proposal_module(&app, &core_addr); - let proposal_id = make_proposal(&mut app, &proposal_module, "one", vec![]); + let proposal_id = make_proposal(&mut app, &proposal_module, "one", vec![], None); let votes = query_list_votes(&app, &proposal_module, proposal_id, None, None); assert_eq!(votes.votes, vec![]); @@ -3828,6 +3969,7 @@ fn test_update_pre_propose_module() { funds: vec![], } .into()], + None, ); vote_on_proposal( @@ -3869,7 +4011,7 @@ fn test_update_pre_propose_module() { ); // Make a new proposal with this new module installed. - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); // Check that the deposit was withdrawn. let balance = query_balance_cw20(&app, gov_token.as_str(), CREATOR_ADDR); assert_eq!(balance, Uint128::new(9_999_999)); @@ -3907,6 +4049,7 @@ fn test_update_pre_propose_module() { funds: vec![], } .into()], + None, ); vote_on_proposal( &mut app, @@ -4003,7 +4146,7 @@ fn test_rational_clobbered_on_revote() { let proposal_module = query_single_proposal_module(&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![]); + let proposal_id = make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); let rationale = Some("to_string".to_string()); @@ -4090,7 +4233,7 @@ fn test_proposal_count_goes_up() { assert_eq!(next, 2); mint_cw20s(&mut app, &gov_token, &core_addr, CREATOR_ADDR, 10_000_000); - make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![]); + make_proposal(&mut app, &proposal_module, CREATOR_ADDR, vec![], None); let next = query_next_proposal_id(&app, &proposal_module); assert_eq!(next, 3);