Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 5850973

Browse files
authored
Governance: Add VoteTipping option (#2826)
* spl-governance: Add VoteTipping option This allows two new tipping styles in addition to the previous "Strict" mode: - Early: Where a yes majority above the yes vote threshold will complete the vote, even if below 50% of total possible votes. - Disabled: Where votes never complete early. * address review nits
1 parent 2e69f37 commit 5850973

File tree

6 files changed

+445
-31
lines changed

6 files changed

+445
-31
lines changed

governance/chat/program/tests/program_test/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ impl GovernanceChatProgramTest {
184184
min_transaction_hold_up_time: 10,
185185
max_voting_time: 10,
186186
vote_threshold_percentage: VoteThresholdPercentage::YesVote(60),
187-
vote_weight_source: spl_governance::state::enums::VoteWeightSource::Deposit,
187+
vote_tipping: spl_governance::state::enums::VoteTipping::Strict,
188188
proposal_cool_off_time: 0,
189189
};
190190

governance/program/src/state/enums.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,26 @@ pub enum VoteThresholdPercentage {
122122
Quorum(u8),
123123
}
124124

125-
/// The source of voter weights used to vote on proposals
125+
/// The type of vote tipping to use on a Proposal.
126+
///
127+
/// Vote tipping means that under some conditions voting will complete early.
126128
#[repr(C)]
127129
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
128-
pub enum VoteWeightSource {
129-
/// Governing token deposits into the Realm are used as voter weights
130-
Deposit,
131-
/// Governing token account snapshots as of the time a proposal entered voting state are used as voter weights
132-
/// Note: Snapshot source is not supported in the current version
133-
/// Support for account snapshots are required in solana and/or arweave as a prerequisite
134-
Snapshot,
130+
pub enum VoteTipping {
131+
/// Tip when there is no way for another option to win and the vote threshold
132+
/// has been reached. This ignores voters withdrawing their votes.
133+
///
134+
/// Currently only supported for the "yes" option in single choice votes.
135+
Strict,
136+
137+
/// Tip when an option reaches the vote threshold and has more vote weight
138+
/// than any other options.
139+
///
140+
/// Currently only supported for the "yes" option in single choice votes.
141+
Early,
142+
143+
/// Never tip the vote early.
144+
Disabled,
135145
}
136146

137147
/// The status of instruction execution

governance/program/src/state/governance.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use crate::{
44
error::GovernanceError,
55
state::{
6-
enums::{GovernanceAccountType, VoteThresholdPercentage, VoteWeightSource},
6+
enums::{GovernanceAccountType, VoteThresholdPercentage, VoteTipping},
77
realm::assert_is_valid_realm,
88
},
99
};
@@ -34,9 +34,8 @@ pub struct GovernanceConfig {
3434
/// Time limit in seconds for proposal to be open for voting
3535
pub max_voting_time: u32,
3636

37-
/// The source of vote weight for voters
38-
/// Note: In the current version only token deposits are accepted as vote weight
39-
pub vote_weight_source: VoteWeightSource,
37+
/// Conditions under which a vote will complete early
38+
pub vote_tipping: VoteTipping,
4039

4140
/// The time period in seconds within which a Proposal can be still cancelled after being voted on
4241
/// Once cool off time expires Proposal can't be cancelled any longer and becomes a law
@@ -288,10 +287,6 @@ pub fn assert_is_valid_governance_config(
288287
}
289288
}
290289

291-
if governance_config.vote_weight_source != VoteWeightSource::Deposit {
292-
return Err(GovernanceError::VoteWeightSourceNotSupported.into());
293-
}
294-
295290
if governance_config.proposal_cool_off_time > 0 {
296291
return Err(GovernanceError::ProposalCoolOffTimeNotSupported.into());
297292
}

governance/program/src/state/proposal.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use crate::{
2525
state::{
2626
enums::{
2727
GovernanceAccountType, InstructionExecutionFlags, MintMaxVoteWeightSource,
28-
ProposalState, TransactionExecutionStatus, VoteThresholdPercentage,
28+
ProposalState, TransactionExecutionStatus, VoteThresholdPercentage, VoteTipping,
2929
},
3030
governance::GovernanceConfig,
3131
proposal_transaction::ProposalTransactionV2,
@@ -542,13 +542,33 @@ impl ProposalV2 {
542542
get_min_vote_threshold_weight(&config.vote_threshold_percentage, max_vote_weight)
543543
.unwrap();
544544

545-
if yes_vote_weight >= min_vote_threshold_weight
546-
&& yes_vote_weight > (max_vote_weight.saturating_sub(yes_vote_weight))
547-
{
548-
yes_option.vote_result = OptionVoteResult::Succeeded;
549-
return Some(ProposalState::Succeeded);
550-
} else if deny_vote_weight > (max_vote_weight.saturating_sub(min_vote_threshold_weight))
551-
|| deny_vote_weight >= (max_vote_weight.saturating_sub(deny_vote_weight))
545+
match config.vote_tipping {
546+
VoteTipping::Disabled => {}
547+
VoteTipping::Strict => {
548+
if yes_vote_weight >= min_vote_threshold_weight
549+
&& yes_vote_weight > (max_vote_weight.saturating_sub(yes_vote_weight))
550+
{
551+
yes_option.vote_result = OptionVoteResult::Succeeded;
552+
return Some(ProposalState::Succeeded);
553+
}
554+
}
555+
VoteTipping::Early => {
556+
if yes_vote_weight >= min_vote_threshold_weight
557+
&& yes_vote_weight > deny_vote_weight
558+
{
559+
yes_option.vote_result = OptionVoteResult::Succeeded;
560+
return Some(ProposalState::Succeeded);
561+
}
562+
}
563+
}
564+
565+
// If vote tipping isn't disabled entirely, allow a vote to complete as
566+
// "defeated" if there is no possible way of reaching majority or the
567+
// min_vote_threshold_weight for another option. This tipping is always
568+
// strict, there's no equivalent to "early" tipping for deny votes.
569+
if config.vote_tipping != VoteTipping::Disabled
570+
&& (deny_vote_weight > (max_vote_weight.saturating_sub(min_vote_threshold_weight))
571+
|| deny_vote_weight >= (max_vote_weight.saturating_sub(deny_vote_weight)))
552572
{
553573
yes_option.vote_result = OptionVoteResult::Defeated;
554574
return Some(ProposalState::Defeated);
@@ -929,7 +949,7 @@ mod test {
929949
use solana_program::clock::Epoch;
930950

931951
use crate::state::{
932-
enums::{MintMaxVoteWeightSource, VoteThresholdPercentage, VoteWeightSource},
952+
enums::{MintMaxVoteWeightSource, VoteThresholdPercentage},
933953
legacy::ProposalV1,
934954
realm::RealmConfig,
935955
vote_record::VoteChoice,
@@ -1036,7 +1056,7 @@ mod test {
10361056
min_transaction_hold_up_time: 10,
10371057
max_voting_time: 5,
10381058
vote_threshold_percentage: VoteThresholdPercentage::YesVote(60),
1039-
vote_weight_source: VoteWeightSource::Deposit,
1059+
vote_tipping: VoteTipping::Strict,
10401060
proposal_cool_off_time: 0,
10411061
}
10421062
}

0 commit comments

Comments
 (0)