diff --git a/contracts/proposal/dao-proposal-single/src/contract.rs b/contracts/proposal/dao-proposal-single/src/contract.rs index 93e73eb76..77985ac0d 100644 --- a/contracts/proposal/dao-proposal-single/src/contract.rs +++ b/contracts/proposal/dao-proposal-single/src/contract.rs @@ -196,10 +196,18 @@ pub fn execute_propose( let expiration = config.max_voting_period.after(&env.block); + // Get Proposer voting power + let proposer_power = get_voting_power( + deps.as_ref(), + proposer.clone(), + &config.dao, + Some(env.block.height), + )?; let total_power = get_total_power(deps.as_ref(), &config.dao, Some(env.block.height))?; let proposal = { // Limit mutability to this block. + // If proposer has voting power = total_power. AutoPass proposal let mut proposal = SingleChoiceProposal { title, description, @@ -210,7 +218,11 @@ pub fn execute_propose( threshold: config.threshold, total_power, msgs, - status: Status::Open, + status: if proposer_power == total_power { + Status::Passed + } else { + Status::Open + }, votes: Votes::zero(), allow_revoting: config.allow_revoting, }; 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 19d3900e9..abc86b589 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/adversarial_tests.rs @@ -22,7 +22,7 @@ use dao_voting::{ voting::Vote, }; -use super::CREATOR_ADDR; +use super::{CREATOR_ADDR, MEMBER_ADDR}; use crate::{query::ProposalResponse, ContractError}; struct CommonTest { @@ -134,6 +134,13 @@ fn test_execute_proposal_more_than_once() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); app.update_block(next_block); diff --git a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs index 3fd485f62..d1e2b4d2a 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/instantiate.rs @@ -21,7 +21,7 @@ use super::{ cw4_group_contract, cw4_voting_contract, cw721_base_contract, cw721_stake_contract, cw_core_contract, native_staked_balances_voting_contract, proposal_single_contract, }, - CREATOR_ADDR, + CREATOR_ADDR, MEMBER_ADDR, }; pub(crate) fn get_pre_propose_info( @@ -96,10 +96,16 @@ pub(crate) fn instantiate_with_staked_cw721_governance( let proposal_module_code_id = app.store_code(proposal_single_contract()); let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] + vec![ + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(100_000_000), + }, + Cw20Coin { + address: MEMBER_ADDR.to_string(), + amount: Uint128::new(100_000_000), + }, + ] }); let initial_balances: Vec = { @@ -545,10 +551,16 @@ pub(crate) fn instantiate_with_cw4_groups_governance( let votemod_id = app.store_code(cw4_voting_contract()); let initial_weights = initial_weights.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(1), - }] + vec![ + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(1), + }, + Cw20Coin { + address: MEMBER_ADDR.to_string(), + amount: Uint128::new(1), + }, + ] }); // Remove duplicates so that we can test duplicate voting. diff --git a/contracts/proposal/dao-proposal-single/src/testing/mod.rs b/contracts/proposal/dao-proposal-single/src/testing/mod.rs index 12c9b9af0..6b61b91ae 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/mod.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/mod.rs @@ -8,3 +8,4 @@ mod queries; mod tests; pub(crate) const CREATOR_ADDR: &str = "creator"; +pub(crate) const MEMBER_ADDR: &str = "dao_member"; diff --git a/contracts/proposal/dao-proposal-single/src/testing/tests.rs b/contracts/proposal/dao-proposal-single/src/testing/tests.rs index a5cc7aa01..30efacbb6 100644 --- a/contracts/proposal/dao-proposal-single/src/testing/tests.rs +++ b/contracts/proposal/dao-proposal-single/src/testing/tests.rs @@ -70,7 +70,7 @@ use super::{ do_votes::do_votes_staked_balances, execute::vote_on_proposal_with_rationale, queries::{query_next_proposal_id, query_vote}, - CREATOR_ADDR, + CREATOR_ADDR, MEMBER_ADDR, }; struct CommonTest { @@ -127,7 +127,7 @@ fn test_simple_propose_staked_balances() { threshold: PercentageThreshold::Majority {}, }, allow_revoting: false, - total_power: Uint128::new(100_000_000), + total_power: Uint128::new(200_000_000), msgs: vec![], status: Status::Open, votes: Votes::zero(), @@ -176,7 +176,7 @@ fn test_simple_proposal_cw4_voting() { quorum: PercentageThreshold::Majority {}, }, allow_revoting: false, - total_power: Uint128::new(1), + total_power: Uint128::new(2), msgs: vec![], status: Status::Open, votes: Votes::zero(), @@ -348,6 +348,13 @@ fn test_proposal_message_execution() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); let proposal = query_proposal(&app, &proposal_module, proposal_id); assert_eq!(proposal.proposal.status, Status::Passed); @@ -522,7 +529,13 @@ fn test_cant_execute_not_member_when_proposal_created() { proposal_id, Vote::Yes, ); - + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); // Give noah some tokens. mint_cw20s(&mut app, &gov_token, &core_addr, "noah", 20_000_000); // Have noah stake some. @@ -601,6 +614,13 @@ fn test_update_config() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); let config = query_proposal_config(&app, &proposal_module); @@ -661,6 +681,13 @@ fn test_anyone_may_propose_and_proposal_listing() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); // Only members can execute still. let err = execute_proposal_should_fail(&mut app, &proposal_module, &addr, proposal_id); assert!(matches!(err, ContractError::Unauthorized {})); @@ -711,11 +738,11 @@ fn test_anyone_may_propose_and_proposal_listing() { threshold: PercentageThreshold::Majority {}, }, allow_revoting: false, - total_power: Uint128::new(100_000_000), + total_power: Uint128::new(200_000_000), msgs: vec![], status: Status::Executed, votes: Votes { - yes: Uint128::new(100_000_000), + yes: Uint128::new(200_000_000), no: Uint128::zero(), abstain: Uint128::zero() }, @@ -1047,6 +1074,13 @@ fn test_min_voting_period_no_early_pass() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); let proposal_response = query_proposal(&app, &proposal_module, proposal_id); assert_eq!(proposal_response.proposal.status, Status::Open); @@ -1235,6 +1269,13 @@ fn test_allow_revoting_config_changes() { revoting_proposal, Vote::No, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + no_revoting_proposal, + Vote::Yes, + ); // Expire the revoting proposal and close it. app.update_block(|b| b.time = b.time.plus_seconds(604800)); close_proposal(&mut app, &proposal_module, CREATOR_ADDR, revoting_proposal); @@ -1882,6 +1923,13 @@ fn test_execution_failed() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); let proposal = query_proposal(&app, &proposal_module, proposal_id); @@ -1933,6 +1981,13 @@ fn test_execution_failed() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + proposal_id, + Vote::Yes, + ); let err: StdError = app .execute_contract( Addr::unchecked(CREATOR_ADDR), @@ -2508,6 +2563,13 @@ fn test_update_pre_propose_module() { proposal_id, Vote::Yes, ); + vote_on_proposal( + &mut app, + &proposal_module, + MEMBER_ADDR, + pre_update_proposal_id, + Vote::Abstain, + ); execute_proposal(&mut app, &proposal_module, CREATOR_ADDR, proposal_id); // Make sure the left over deposit was returned to the DAO. diff --git a/contracts/test/dao-proposal-hook-counter/src/tests.rs b/contracts/test/dao-proposal-hook-counter/src/tests.rs index fddc0f104..2a6137065 100644 --- a/contracts/test/dao-proposal-hook-counter/src/tests.rs +++ b/contracts/test/dao-proposal-hook-counter/src/tests.rs @@ -16,6 +16,7 @@ use dao_proposal_single::state::Config; use dao_voting::proposal::SingleChoiceProposeMsg as ProposeMsg; const CREATOR_ADDR: &str = "creator"; +const MEMBER_ADDR: &str = "dao_member"; fn cw20_contract() -> Box> { let contract = ContractWrapper::new( @@ -92,10 +93,16 @@ fn instantiate_with_default_governance( let votemod_id = app.store_code(cw20_balances_voting()); let initial_balances = initial_balances.unwrap_or_else(|| { - vec![Cw20Coin { - address: CREATOR_ADDR.to_string(), - amount: Uint128::new(100_000_000), - }] + vec![ + Cw20Coin { + address: CREATOR_ADDR.to_string(), + amount: Uint128::new(100_000_000), + }, + Cw20Coin { + address: MEMBER_ADDR.to_string(), + amount: Uint128::new(100_000_000), + }, + ] }); let governance_instantiate = dao_interface::msg::InstantiateMsg { @@ -297,13 +304,23 @@ fn test_counters() { &[], ) .unwrap(); - - // Query vote counter, expect 1 + app.execute_contract( + Addr::unchecked(MEMBER_ADDR.to_string()), + govmod_single.clone(), + &dao_proposal_single::msg::ExecuteMsg::Vote { + proposal_id: 1, + vote: Vote::Yes, + rationale: None, + }, + &[], + ) + .unwrap(); + // Query vote counter, expect 2 let resp: CountResponse = app .wrap() .query_wasm_smart(counters.clone(), &QueryMsg::VoteCounter {}) .unwrap(); - assert_eq!(resp.count, 1); + assert_eq!(resp.count, 2); // Query status changed counter, expect 1 let resp: CountResponse = app @@ -419,18 +436,18 @@ fn test_counters() { .unwrap(); // The success counters should still work - // Query vote counter, expect 2 + // Query vote counter, expect 3 let resp: CountResponse = app .wrap() .query_wasm_smart(counters.clone(), &QueryMsg::VoteCounter {}) .unwrap(); - assert_eq!(resp.count, 2); - // Query status changed counter, expect 2 + assert_eq!(resp.count, 3); + // Query status changed counter, expect 1 let resp: CountResponse = app .wrap() .query_wasm_smart(counters, &QueryMsg::StatusChangedCounter {}) .unwrap(); - assert_eq!(resp.count, 2); + assert_eq!(resp.count, 1); // The contract should of removed the failing counters let hooks: HooksResponse = app diff --git a/packages/dao-testing/src/tests.rs b/packages/dao-testing/src/tests.rs index d57377333..3699f0358 100644 --- a/packages/dao-testing/src/tests.rs +++ b/packages/dao-testing/src/tests.rs @@ -45,14 +45,22 @@ where ); do_votes( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::No, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], + vec![ + TestSingleChoiceVote { + voter: "zeke".to_string(), + position: Vote::Yes, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "member".to_string(), + position: Vote::No, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + ], Threshold::AbsolutePercentage { - percentage: PercentageThreshold::Percent(Decimal::percent(100)), + percentage: PercentageThreshold::Percent(Decimal::percent(90)), }, Status::Rejected, None, @@ -157,12 +165,20 @@ where F: Fn(Vec, Threshold, Status, Option), { do_votes( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::Abstain, - weight: Uint128::new(u64::max_value().into()), - should_execute: ShouldExecute::Yes, - }], + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(u64::max_value().into()), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "n1n0".to_string(), + position: Vote::Abstain, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + ], Threshold::AbsolutePercentage { percentage: PercentageThreshold::Percent(Decimal::percent(100)), }, @@ -174,12 +190,20 @@ where // rejected. for i in 0..101 { do_votes( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::Abstain, - weight: Uint128::new(u64::max_value().into()), - should_execute: ShouldExecute::Yes, - }], + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(u64::max_value().into()), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "tere".to_string(), + position: Vote::Abstain, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + ], Threshold::ThresholdQuorum { threshold: PercentageThreshold::Percent(Decimal::percent(100)), quorum: PercentageThreshold::Percent(Decimal::percent(i)), @@ -213,12 +237,20 @@ where ); do_votes( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::Yes, - weight: Uint128::new(10), - should_execute: ShouldExecute::Yes, - }], + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "prop1".to_string(), + position: Vote::Yes, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }, + ], Threshold::AbsolutePercentage { percentage: PercentageThreshold::Percent(Decimal::percent(1)), }, @@ -228,12 +260,20 @@ where // HIGH PERCISION do_votes( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::Yes, - weight: Uint128::new(9999999), - should_execute: ShouldExecute::Yes, - }], + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Yes, + weight: Uint128::new(9999999), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "prop2".to_string(), + position: Vote::Yes, + weight: Uint128::new(10), + should_execute: ShouldExecute::Yes, + }, + ], Threshold::AbsolutePercentage { percentage: PercentageThreshold::Percent(Decimal::percent(1)), }, @@ -242,12 +282,20 @@ where ); do_votes( - vec![TestSingleChoiceVote { - voter: "ekez".to_string(), - position: Vote::Abstain, - weight: Uint128::new(1), - should_execute: ShouldExecute::Yes, - }], + vec![ + TestSingleChoiceVote { + voter: "ekez".to_string(), + position: Vote::Abstain, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }, + TestSingleChoiceVote { + voter: "teze".to_string(), + position: Vote::Abstain, + weight: Uint128::new(1), + should_execute: ShouldExecute::Yes, + }, + ], Threshold::AbsolutePercentage { percentage: PercentageThreshold::Percent(Decimal::percent(1)), },