Skip to content

Commit

Permalink
ensure early proposal outcomes are a result of non-delegated votes, s…
Browse files Browse the repository at this point in the history
…ince those may change
  • Loading branch information
NoahSaso committed Dec 24, 2024
1 parent ed4a6f8 commit b84be37
Show file tree
Hide file tree
Showing 19 changed files with 511 additions and 179 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@
"type": "object",
"required": [
"height",
"individual_power",
"is_first_vote",
"power",
"proposal_id",
Expand All @@ -569,12 +570,20 @@
"format": "uint64",
"minimum": 0.0
},
"individual_power": {
"description": "The individual voting power of the voter (excluding any delegated voting power).",
"allOf": [
{
"$ref": "#/definitions/Uint128"
}
]
},
"is_first_vote": {
"description": "Whether this is the first vote cast by this voter on this proposal. This will always be true if revoting is disabled.",
"type": "boolean"
},
"power": {
"description": "The voting power of the voter.",
"description": "The total voting power of the voter.",
"allOf": [
{
"$ref": "#/definitions/Uint128"
Expand Down
12 changes: 0 additions & 12 deletions contracts/delegation/dao-vote-delegation/src/testing/suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,16 +451,4 @@ impl DaoVoteDelegationTestingSuite {
);
assert_eq!(udvp.effective, effective.into());
}

/// assert vote count on single choice proposal
pub fn assert_single_choice_votes_count(
&self,
proposal_module: impl Into<String>,
proposal_id: u64,
vote: dao_voting::voting::Vote,
count: impl Into<Uint128>,
) {
let proposal = self.get_single_choice_proposal(proposal_module, proposal_id);
assert_eq!(proposal.votes.get(vote), count.into());
}
}
100 changes: 100 additions & 0 deletions contracts/delegation/dao-vote-delegation/src/testing/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,106 @@ fn test_vote_with_override() {
);
}

#[test]
fn test_overrideable_vote_doesnt_end_proposal_early() {
let mut suite = DaoVoteDelegationTestingSuite::new().build();
let dao = suite.dao.clone();

// register ADDR0 and ADDR1 as delegates
suite.register(ADDR0);
suite.register(ADDR1);

// delegate all of ADDR2's and ADDR3's voting power to ADDR0
suite.delegate(ADDR2, ADDR0, Decimal::percent(100));
suite.delegate(ADDR3, ADDR0, Decimal::percent(100));
// delegate all of ADDR4's voting power to ADDR1
suite.delegate(ADDR4, ADDR1, Decimal::percent(100));

// delegations take effect on the next block
suite.advance_block();

// ensure delegations are correctly applied
suite.assert_delegations_count(ADDR2, 1);
suite.assert_delegations_count(ADDR3, 1);
suite.assert_delegations_count(ADDR4, 1);

// propose a proposal
let (proposal_module, id1, p1) =
suite.propose_single_choice(&dao, ADDR2, "test proposal", vec![]);

// ADDR0 has 100% of ADDR2's voting power and 100% of ADDR3's voting power
suite.assert_effective_udvp(
ADDR0,
&proposal_module,
id1,
p1.start_height,
suite.members[2].weight + suite.members[3].weight,
);
// ADDR1 has 100% of ADDR4's voting power
suite.assert_effective_udvp(
ADDR1,
&proposal_module,
id1,
p1.start_height,
suite.members[4].weight,
);

// delegate ADDR0 votes on proposal
suite.vote_single_choice(&dao, ADDR0, id1, dao_voting::voting::Vote::Yes);

// proposal should not pass early, even though sufficient voting power has
// voted for the configured threshold/quorum, because the delegators can
// override the delegate's vote and change the outcome
suite.assert_single_choice_status(&proposal_module, id1, dao_voting::status::Status::Open);

// ADDR0 votes with own voting power, 100% of ADDR2's voting power, and 100%
// of ADDR3's voting power
suite.assert_single_choice_votes_count(
&proposal_module,
id1,
dao_voting::voting::Vote::Yes,
suite.members[0].weight + suite.members[2].weight + suite.members[3].weight,
);

// ADDR2 overrides ADDR0's vote
suite.vote_single_choice(&dao, ADDR2, id1, dao_voting::voting::Vote::No);
// ADDR0's unvoted delegated voting power should no longer include ADDR2's
// voting power on this proposal
suite.assert_effective_udvp(
ADDR0,
&proposal_module,
id1,
p1.start_height,
suite.members[3].weight,
);
// vote counts should change to reflect removed (overridden) delegate vote
suite.assert_single_choice_votes_count(
&proposal_module,
id1,
dao_voting::voting::Vote::Yes,
suite.members[0].weight + suite.members[3].weight,
);
suite.assert_single_choice_votes_count(
&proposal_module,
id1,
dao_voting::voting::Vote::No,
suite.members[2].weight,
);

// proposal should still be open since only ADDR0's personal voting power
// (1) and ADDR2's voting power (3) has been counted as definitive votes.
// The remaining 6 voting power has either not been used to cast a vote or
// is defaulting to a delegate's vote but can still be overridden.
suite.assert_single_choice_status(&proposal_module, id1, dao_voting::status::Status::Open);

// delegator ADDR3 votes, adding their 3 VP to ADDR2's 3 VP, meaning the
// outcome is now determined to be No.
suite.vote_single_choice(&dao, ADDR3, id1, dao_voting::voting::Vote::No);

// proposal should be rejected since the outcome is now determined to be No
suite.assert_single_choice_status(&proposal_module, id1, dao_voting::status::Status::Rejected);
}

#[test]
fn test_allow_register_after_unregister_same_block() {
let mut suite = DaoVoteDelegationTestingSuite::new().build();
Expand Down
3 changes: 2 additions & 1 deletion contracts/external/dao-migrator/src/testing/state_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ pub fn query_proposal_v1(
total_power: proposal.total_power,
msgs: proposal.msgs,
status: v1_status_to_v2(proposal.status),
votes: v1_votes_to_v2(proposal.votes),
votes: v1_votes_to_v2(proposal.votes.clone()),
individual_votes: v1_votes_to_v2(proposal.votes),
allow_revoting: proposal.allow_revoting,
veto: None,
delegation_module: None,
Expand Down
3 changes: 2 additions & 1 deletion contracts/external/dao-migrator/src/utils/state_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ pub fn query_proposal_v1(
total_power: proposal.total_power,
msgs: proposal.msgs,
status: v1_status_to_v2(proposal.status),
votes: v1_votes_to_v2(proposal.votes),
votes: v1_votes_to_v2(proposal.votes.clone()),
individual_votes: v1_votes_to_v2(proposal.votes),
allow_revoting: proposal.allow_revoting,
veto: None,
delegation_module: None,
Expand Down
55 changes: 26 additions & 29 deletions contracts/pre-propose/dao-pre-propose-approval-single/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use dao_interface::proposal::InfoResponse;
use dao_interface::state::ProposalModule;
use dao_interface::state::{Admin, ModuleInstantiateInfo};
use dao_pre_propose_base::{error::PreProposeError, msg::DepositInfoResponse, state::Config};
use dao_proposal_single::query::ProposalResponse;
use dao_proposal_single as dps;
use dao_testing::{
contracts::{
cw20_base_contract, cw4_group_contract, dao_pre_propose_approval_single_contract,
Expand Down Expand Up @@ -44,7 +44,7 @@ fn get_default_proposal_module_instantiate(
app: &mut App,
deposit_info: Option<UncheckedDepositInfo>,
open_proposal_submission: bool,
) -> dao_proposal_single::msg::InstantiateMsg {
) -> dps::msg::InstantiateMsg {
let pre_propose_id = app.store_code(dao_pre_propose_approval_single_contract());

let submission_policy = if open_proposal_submission {
Expand All @@ -57,7 +57,7 @@ fn get_default_proposal_module_instantiate(
}
};

dao_proposal_single::msg::InstantiateMsg {
dps::msg::InstantiateMsg {
threshold: Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Majority {},
},
Expand Down Expand Up @@ -159,7 +159,7 @@ fn setup_default_test(
.wrap()
.query_wasm_smart(
proposal_single.clone(),
&dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {},
&dps::msg::QueryMsg::ProposalCreationPolicy {},
)
.unwrap();

Expand Down Expand Up @@ -269,7 +269,7 @@ fn vote(app: &mut App, module: Addr, sender: &str, id: u64, position: Vote) -> S
app.execute_contract(
Addr::unchecked(sender),
module.clone(),
&dao_proposal_single::msg::ExecuteMsg::Vote {
&dps::msg::ExecuteMsg::Vote {
proposal_id: id,
vote: position,
rationale: None,
Expand All @@ -278,12 +278,9 @@ fn vote(app: &mut App, module: Addr, sender: &str, id: u64, position: Vote) -> S
)
.unwrap();

let proposal: ProposalResponse = app
let proposal: dps::query::ProposalResponse = app
.wrap()
.query_wasm_smart(
module,
&dao_proposal_single::msg::QueryMsg::Proposal { proposal_id: id },
)
.query_wasm_smart(module, &dps::msg::QueryMsg::Proposal { proposal_id: id })
.unwrap();

proposal.proposal.status
Expand Down Expand Up @@ -403,7 +400,7 @@ fn close_proposal(app: &mut App, module: Addr, sender: &str, proposal_id: u64) {
app.execute_contract(
Addr::unchecked(sender),
module,
&dao_proposal_single::msg::ExecuteMsg::Close { proposal_id },
&dps::msg::ExecuteMsg::Close { proposal_id },
&[],
)
.unwrap();
Expand All @@ -413,7 +410,7 @@ fn execute_proposal(app: &mut App, module: Addr, sender: &str, proposal_id: u64)
app.execute_contract(
Addr::unchecked(sender),
module,
&dao_proposal_single::msg::ExecuteMsg::Execute { proposal_id },
&dps::msg::ExecuteMsg::Execute { proposal_id },
&[],
)
.unwrap();
Expand Down Expand Up @@ -1667,7 +1664,7 @@ fn test_instantiate_with_zero_native_deposit() {
let proposal_module_instantiate = {
let pre_propose_id = app.store_code(dao_pre_propose_approval_single_contract());

dao_proposal_single::msg::InstantiateMsg {
dps::msg::InstantiateMsg {
threshold: Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Majority {},
},
Expand Down Expand Up @@ -1737,7 +1734,7 @@ fn test_instantiate_with_zero_cw20_deposit() {
let proposal_module_instantiate = {
let pre_propose_id = app.store_code(dao_pre_propose_approval_single_contract());

dao_proposal_single::msg::InstantiateMsg {
dps::msg::InstantiateMsg {
threshold: Threshold::AbsolutePercentage {
percentage: PercentageThreshold::Majority {},
},
Expand Down Expand Up @@ -2537,7 +2534,7 @@ fn test_withdraw() {
.wrap()
.query_wasm_smart(
proposal_single.clone(),
&dao_proposal_single::msg::QueryMsg::ProposalCreationPolicy {},
&dps::msg::QueryMsg::ProposalCreationPolicy {},
)
.unwrap();

Expand Down Expand Up @@ -2865,27 +2862,27 @@ fn test_migrate_from_v241() {
app.execute_contract(
Addr::unchecked("approver"),
pre_propose.clone(),
&dppas_v241::msg::ExecuteMsg::Extension {
msg: dppas_v241::msg::ExecuteExt::Approve { id: 3 },
&ExecuteMsg::Extension {
msg: ExecuteExt::Approve { id: 3 },
},
&[],
)
.unwrap();
app.execute_contract(
Addr::unchecked("ekez"),
proposal_single.clone(),
&dao_proposal_single::msg::ExecuteMsg::Execute { proposal_id: 3 },
&dps_v241::msg::ExecuteMsg::Execute { proposal_id: 3 },
&[],
)
.unwrap();
let proposal: ProposalResponse = app
let proposal: dps_v241::query::ProposalResponse = app
.wrap()
.query_wasm_smart(
proposal_single.clone(),
&dao_proposal_single::msg::QueryMsg::Proposal { proposal_id: 3 },
&dps_v241::msg::QueryMsg::Proposal { proposal_id: 3 },
)
.unwrap();
assert_eq!(proposal.proposal.status, Status::Executed);
assert_eq!(proposal.proposal.status, dv_v241::status::Status::Executed);
}

#[test]
Expand Down Expand Up @@ -3119,8 +3116,8 @@ fn test_migrate_from_v241_with_policy_update() {
app.execute_contract(
Addr::unchecked("approver"),
pre_propose.clone(),
&dppas_v241::msg::ExecuteMsg::Extension {
msg: dppas_v241::msg::ExecuteExt::Approve { id: 2 },
&ExecuteMsg::Extension {
msg: ExecuteExt::Approve { id: 2 },
},
&[],
)
Expand Down Expand Up @@ -3226,9 +3223,9 @@ fn test_migrate_from_v241_with_policy_update() {
app.execute_contract(
Addr::unchecked("ekez"),
proposal_single.clone(),
&dao_proposal_single::msg::ExecuteMsg::Vote {
&dps_v241::msg::ExecuteMsg::Vote {
proposal_id: 3,
vote: Vote::Yes,
vote: dv_v241::voting::Vote::Yes,
rationale: None,
},
&[],
Expand All @@ -3237,16 +3234,16 @@ fn test_migrate_from_v241_with_policy_update() {
app.execute_contract(
Addr::unchecked("ekez"),
proposal_single.clone(),
&dao_proposal_single::msg::ExecuteMsg::Execute { proposal_id: 3 },
&dps_v241::msg::ExecuteMsg::Execute { proposal_id: 3 },
&[],
)
.unwrap();
let proposal: ProposalResponse = app
let proposal: dps_v241::query::ProposalResponse = app
.wrap()
.query_wasm_smart(
proposal_single.clone(),
&dao_proposal_single::msg::QueryMsg::Proposal { proposal_id: 3 },
&dps_v241::msg::QueryMsg::Proposal { proposal_id: 3 },
)
.unwrap();
assert_eq!(proposal.proposal.status, Status::Executed);
assert_eq!(proposal.proposal.status, dv_v241::status::Status::Executed);
}
Loading

0 comments on commit b84be37

Please sign in to comment.