Skip to content

Commit ee2a848

Browse files
authored
Revoting multiple (DA0-DA0#433)
* allow revoting on prop-multiple * proposal revoting tests * tests; nullifying existing vote in case of a revote * simplifying tests; formatting * cargo schema; types * checked arithmetics when adding & removing votes; improving majority revote rejection prop test * mapping errors when doing checked arithmetic on multiple choice votes * handling old vote removal within ballots update closure * updating tests
1 parent e3c72d4 commit ee2a848

File tree

15 files changed

+910
-18
lines changed

15 files changed

+910
-18
lines changed

contracts/cw-proposal-multiple/schema/config_response.json

+5
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@
44
"description": "The governance module's configuration.",
55
"type": "object",
66
"required": [
7+
"allow_revoting",
78
"close_proposal_on_execution_failure",
89
"dao",
910
"max_voting_period",
1011
"only_members_execute",
1112
"voting_strategy"
1213
],
1314
"properties": {
15+
"allow_revoting": {
16+
"description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.",
17+
"type": "boolean"
18+
},
1419
"close_proposal_on_execution_failure": {
1520
"description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.",
1621
"type": "boolean"

contracts/cw-proposal-multiple/schema/execute_msg.json

+5
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,18 @@
129129
"update_config": {
130130
"type": "object",
131131
"required": [
132+
"allow_revoting",
132133
"close_proposal_on_execution_failure",
133134
"dao",
134135
"max_voting_period",
135136
"only_members_execute",
136137
"voting_strategy"
137138
],
138139
"properties": {
140+
"allow_revoting": {
141+
"description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.",
142+
"type": "boolean"
143+
},
139144
"close_proposal_on_execution_failure": {
140145
"description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.",
141146
"type": "boolean"

contracts/cw-proposal-multiple/schema/instantiate_msg.json

+5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@
33
"title": "InstantiateMsg",
44
"type": "object",
55
"required": [
6+
"allow_revoting",
67
"close_proposal_on_execution_failure",
78
"max_voting_period",
89
"only_members_execute",
910
"voting_strategy"
1011
],
1112
"properties": {
13+
"allow_revoting": {
14+
"description": "Allows changing votes before the proposal expires. If this is enabled proposals will not be able to complete early as final vote information is not known until the time of proposal expiration.",
15+
"type": "boolean"
16+
},
1217
"close_proposal_on_execution_failure": {
1318
"description": "If set to true proposals will be closed if their execution fails. Otherwise, proposals will remain open after execution failure. For example, with this enabled a proposal to send 5 tokens out of a DAO's treasury with 4 tokens would be closed when it is executed. With this disabled, that same proposal would remain open until the DAO's treasury was large enough for it to be executed.",
1419
"type": "boolean"

contracts/cw-proposal-multiple/schema/list_proposals_response.json

+4
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@
569569
"MultipleChoiceProposal": {
570570
"type": "object",
571571
"required": [
572+
"allow_revoting",
572573
"choices",
573574
"description",
574575
"expiration",
@@ -581,6 +582,9 @@
581582
"voting_strategy"
582583
],
583584
"properties": {
585+
"allow_revoting": {
586+
"type": "boolean"
587+
},
584588
"choices": {
585589
"type": "array",
586590
"items": {

contracts/cw-proposal-multiple/schema/proposal_response.json

+4
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@
573573
"MultipleChoiceProposal": {
574574
"type": "object",
575575
"required": [
576+
"allow_revoting",
576577
"choices",
577578
"description",
578579
"expiration",
@@ -585,6 +586,9 @@
585586
"voting_strategy"
586587
],
587588
"properties": {
589+
"allow_revoting": {
590+
"type": "boolean"
591+
},
588592
"choices": {
589593
"type": "array",
590594
"items": {

contracts/cw-proposal-multiple/schema/reverse_proposals_response.json

+4
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@
569569
"MultipleChoiceProposal": {
570570
"type": "object",
571571
"required": [
572+
"allow_revoting",
572573
"choices",
573574
"description",
574575
"expiration",
@@ -581,6 +582,9 @@
581582
"voting_strategy"
582583
],
583584
"properties": {
585+
"allow_revoting": {
586+
"type": "boolean"
587+
},
584588
"choices": {
585589
"type": "array",
586590
"items": {

contracts/cw-proposal-multiple/src/contract.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub fn instantiate(
6161
min_voting_period,
6262
max_voting_period,
6363
only_members_execute: msg.only_members_execute,
64+
allow_revoting: msg.allow_revoting,
6465
dao,
6566
deposit_info,
6667
close_proposal_on_execution_failure: msg.close_proposal_on_execution_failure,
@@ -96,6 +97,7 @@ pub fn execute(
9697
min_voting_period,
9798
max_voting_period,
9899
only_members_execute,
100+
allow_revoting,
99101
dao,
100102
deposit_info,
101103
close_proposal_on_execution_failure,
@@ -106,6 +108,7 @@ pub fn execute(
106108
min_voting_period,
107109
max_voting_period,
108110
only_members_execute,
111+
allow_revoting,
109112
dao,
110113
deposit_info,
111114
close_proposal_on_execution_failure,
@@ -176,6 +179,7 @@ pub fn execute_propose(
176179
total_power,
177180
status: Status::Open,
178181
votes: MultipleChoiceVotes::zero(checked_multiple_choice_options.len()),
182+
allow_revoting: config.allow_revoting,
179183
deposit_info: config.deposit_info.clone(),
180184
choices: checked_multiple_choice_options,
181185
};
@@ -256,7 +260,26 @@ pub fn execute_vote(
256260
deps.storage,
257261
(proposal_id, info.sender.clone()),
258262
|bal| match bal {
259-
Some(_) => Err(ContractError::AlreadyVoted {}),
263+
Some(current_ballot) => {
264+
if prop.allow_revoting {
265+
if current_ballot.vote == vote {
266+
// Don't allow casting the same vote more than
267+
// once. This seems liable to be confusing
268+
// behavior.
269+
Err(ContractError::AlreadyCast {})
270+
} else {
271+
// Remove the old vote if this is a re-vote.
272+
prop.votes
273+
.remove_vote(current_ballot.vote, current_ballot.power)?;
274+
Ok(Ballot {
275+
power: vote_power,
276+
vote,
277+
})
278+
}
279+
} else {
280+
Err(ContractError::AlreadyVoted {})
281+
}
282+
}
260283
None => Ok(Ballot {
261284
vote,
262285
power: vote_power,
@@ -265,7 +288,8 @@ pub fn execute_vote(
265288
)?;
266289

267290
let old_status = prop.status;
268-
prop.votes.add_vote(vote, vote_power);
291+
292+
prop.votes.add_vote(vote, vote_power)?;
269293
prop.update_status(&env.block)?;
270294
PROPOSALS.save(deps.storage, proposal_id, &prop)?;
271295
let new_status = prop.status;
@@ -440,6 +464,7 @@ pub fn execute_update_config(
440464
min_voting_period: Option<Duration>,
441465
max_voting_period: Duration,
442466
only_members_execute: bool,
467+
allow_revoting: bool,
443468
dao: String,
444469
deposit_info: Option<DepositInfo>,
445470
close_proposal_on_execution_failure: bool,
@@ -468,6 +493,7 @@ pub fn execute_update_config(
468493
min_voting_period,
469494
max_voting_period,
470495
only_members_execute,
496+
allow_revoting,
471497
dao,
472498
deposit_info,
473499
close_proposal_on_execution_failure,

contracts/cw-proposal-multiple/src/error.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,12 @@ pub enum ContractError {
4040
#[error("Not registered to vote (no voting power) at time of proposal creation.")]
4141
NotRegistered {},
4242

43-
#[error("Already voted")]
43+
#[error("Already voted. This proposal does not support revoting.")]
4444
AlreadyVoted {},
4545

46+
#[error("Already cast a vote with that option. Change your vote to revote.")]
47+
AlreadyCast {},
48+
4649
#[error("Proposal must be in 'passed' state to be executed.")]
4750
NotPassed {},
4851

contracts/cw-proposal-multiple/src/msg.rs

+10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ pub struct InstantiateMsg {
2222
/// proposals. Otherwise, any address may execute a passed
2323
/// proposal.
2424
pub only_members_execute: bool,
25+
/// Allows changing votes before the proposal expires. If this is
26+
/// enabled proposals will not be able to complete early as final
27+
/// vote information is not known until the time of proposal
28+
/// expiration.
29+
pub allow_revoting: bool,
2530
/// Information about the deposit required to create a
2631
/// proposal. None if there is no deposit requirement, Some
2732
/// otherwise.
@@ -88,6 +93,11 @@ pub enum ExecuteMsg {
8893
/// proposals. Otherwise, any address may execute a passed
8994
/// proposal. Applies to all outstanding and future proposals.
9095
only_members_execute: bool,
96+
/// Allows changing votes before the proposal expires. If this is
97+
/// enabled proposals will not be able to complete early as final
98+
/// vote information is not known until the time of proposal
99+
/// expiration.
100+
allow_revoting: bool,
91101
/// The address if tge DAO that this governance module is
92102
/// associated with.
93103
dao: String,

0 commit comments

Comments
 (0)