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

Commit 3a21fca

Browse files
authored
Governance: future proof v2 Proposal account (#2837)
* wip: add Veto and Abstain votes to Proposal * chore: rename max_executable_options to max_voter_options * wip: add start_at to Proposal * feat: add SignOffProposal to proposal actions * chore: rename start_at to start_voting_at * feat: add max_voting_time * chore: add comments * chore: update comments * feat: Add Abstain and Veto vote types
1 parent 851efed commit 3a21fca

File tree

10 files changed

+140
-43
lines changed

10 files changed

+140
-43
lines changed

governance/addin-api/src/voter_weight.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ pub enum VoterWeightAction {
1818

1919
/// Create a proposal for a governance. Target: Governance
2020
CreateProposal,
21+
22+
/// Signs off a proposal for a governance. Target: Proposal
23+
/// Note: SignOffProposal is not supported in the current version
24+
SignOffProposal,
2125
}
2226

2327
/// VoterWeightRecord account

governance/program/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,10 @@ pub enum GovernanceError {
381381
/// MaxVoterWeightRecord expired
382382
#[error("MaxVoterWeightRecord expired")]
383383
MaxVoterWeightRecordExpired,
384+
385+
/// Not supported VoteType
386+
#[error("Not supported VoteType")]
387+
NotSupportedVoteType,
384388
}
385389

386390
impl PrintProgramError for GovernanceError {

governance/program/src/processor/process_cast_vote.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ pub fn process_cast_vote(
134134
.unwrap(),
135135
)
136136
}
137+
Vote::Abstain | Vote::Veto => {
138+
return Err(GovernanceError::NotSupportedVoteType.into());
139+
}
137140
}
138141

139142
let max_voter_weight = proposal_data.resolve_max_voter_weight(

governance/program/src/processor/process_create_proposal.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub fn process_create_proposal(
132132
name,
133133
description_link,
134134

135+
start_voting_at: None,
135136
draft_at: clock.unix_timestamp,
136137
signing_off_at: None,
137138
voting_at: None,
@@ -146,8 +147,14 @@ pub fn process_create_proposal(
146147
options: proposal_options,
147148
deny_vote_weight,
148149

150+
veto_vote_weight: None,
151+
abstain_vote_weight: None,
152+
149153
max_vote_weight: None,
154+
max_voting_time: None,
150155
vote_threshold_percentage: None,
156+
157+
reserved: [0; 8],
151158
};
152159

153160
create_and_serialize_account_signed::<ProposalV2>(

governance/program/src/processor/process_relinquish_vote.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ use solana_program::{
99
};
1010
use spl_governance_tools::account::dispose_account;
1111

12-
use crate::state::{
13-
enums::ProposalState,
14-
governance::get_governance_data,
15-
proposal::get_proposal_data_for_governance_and_governing_mint,
16-
token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
17-
vote_record::{get_vote_record_data_for_proposal_and_token_owner, Vote},
12+
use crate::{
13+
error::GovernanceError,
14+
state::{
15+
enums::ProposalState,
16+
governance::get_governance_data,
17+
proposal::get_proposal_data_for_governance_and_governing_mint,
18+
token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
19+
vote_record::{get_vote_record_data_for_proposal_and_token_owner, Vote},
20+
},
1821
};
1922

2023
use borsh::BorshSerialize;
@@ -88,6 +91,9 @@ pub fn process_relinquish_vote(program_id: &Pubkey, accounts: &[AccountInfo]) ->
8891
.unwrap(),
8992
)
9093
}
94+
Vote::Abstain | Vote::Veto => {
95+
return Err(GovernanceError::NotSupportedVoteType.into());
96+
}
9197
}
9298

9399
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;

governance/program/src/state/proposal.rs

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,21 +82,22 @@ pub enum VoteType {
8282
SingleChoice,
8383

8484
/// Multiple options can be selected with up to max_voter_options per voter
85-
/// and with up to max_executable_options of wining options eligible for execution
85+
/// and with up to max_winning_options of successful options
8686
/// Ex. voters are given 5 options, can choose up to 3 (max_voter_options)
87-
/// and only 1 (max_executable_options) wining option can be executed
87+
/// and only 1 (max_winning_options) option can win and be executed
8888
MultiChoice {
8989
/// The max number of options a voter can choose
9090
/// By default it equals to the number of available options
9191
/// Note: In the current version the limit is not supported and not enforced yet
9292
#[allow(dead_code)]
9393
max_voter_options: u16,
9494

95-
/// The max number of wining options which can be executed
95+
/// The max number of wining options
96+
/// For executable proposals it limits how many options can be executed for a Proposal
9697
/// By default it equals to the number of available options
9798
/// Note: In the current version the limit is not supported and not enforced yet
9899
#[allow(dead_code)]
99-
max_executable_options: u16,
100+
max_winning_options: u16,
100101
},
101102
}
102103

@@ -132,12 +133,24 @@ pub struct ProposalV2 {
132133
/// Proposal options
133134
pub options: Vec<ProposalOption>,
134135

135-
/// The weight of the Proposal rejection votes
136+
/// The total weight of the Proposal rejection votes
136137
/// If the proposal has no deny option then the weight is None
137138
/// Only proposals with the deny option can have executable instructions attached to them
138139
/// Without the deny option a proposal is only non executable survey
139140
pub deny_vote_weight: Option<u64>,
140141

142+
/// The total weight of Veto votes
143+
/// Note: Veto is not supported in the current version
144+
pub veto_vote_weight: Option<u64>,
145+
146+
/// The total weight of votes
147+
/// Note: Abstain is not supported in the current version
148+
pub abstain_vote_weight: Option<u64>,
149+
150+
/// Optional start time if the Proposal should not enter voting state immediately after being signed off
151+
/// Note: start_at is not supported in the current version
152+
pub start_voting_at: Option<UnixTimestamp>,
153+
141154
/// When the Proposal was created and entered Draft state
142155
pub draft_at: UnixTimestamp,
143156

@@ -169,11 +182,19 @@ pub struct ProposalV2 {
169182
/// after vote was completed.
170183
pub max_vote_weight: Option<u64>,
171184

185+
/// Max voting time for the proposal if different from parent Governance (only higher value possible)
186+
/// Note: This field is not used in the current version
187+
pub max_voting_time: Option<u32>,
188+
172189
/// The vote threshold percentage at the time Proposal was decided
173190
/// It's used to show correct vote results for historical proposals in cases when the threshold
174191
/// was changed for governance config after vote was completed.
192+
/// TODO: Use this field to override for the threshold from parent Governance (only higher value possible)
175193
pub vote_threshold_percentage: Option<VoteThresholdPercentage>,
176194

195+
/// Reserved space for future versions
196+
pub reserved: [u8; 8],
197+
177198
/// Proposal name
178199
pub name: String,
179200

@@ -184,7 +205,7 @@ pub struct ProposalV2 {
184205
impl AccountMaxSize for ProposalV2 {
185206
fn get_max_size(&self) -> Option<usize> {
186207
let options_size: usize = self.options.iter().map(|o| o.label.len() + 19).sum();
187-
Some(self.name.len() + self.description_link.len() + options_size + 201)
208+
Some(self.name.len() + self.description_link.len() + options_size + 241)
188209
}
189210
}
190211

@@ -372,7 +393,7 @@ impl ProposalV2 {
372393
}
373394
VoteType::MultiChoice {
374395
max_voter_options: _n,
375-
max_executable_options: _m,
396+
max_winning_options: _m,
376397
} => {
377398
// If any option succeeded for multi choice then the proposal as a whole succeeded as well
378399
ProposalState::Succeeded
@@ -708,7 +729,7 @@ impl ProposalV2 {
708729
}
709730
VoteType::MultiChoice {
710731
max_voter_options: _n,
711-
max_executable_options: _m,
732+
max_winning_options: _m,
712733
} => {
713734
if choice_count == 0 {
714735
return Err(GovernanceError::InvalidVote.into());
@@ -721,6 +742,9 @@ impl ProposalV2 {
721742
return Err(GovernanceError::InvalidVote.into());
722743
}
723744
}
745+
Vote::Abstain | Vote::Veto => {
746+
return Err(GovernanceError::NotSupportedVoteType.into());
747+
}
724748
}
725749

726750
Ok(())
@@ -733,6 +757,22 @@ impl ProposalV2 {
733757
} else if self.account_type == GovernanceAccountType::ProposalV1 {
734758
// V1 account can't be resized and we have to translate it back to the original format
735759

760+
if self.abstain_vote_weight.is_some() {
761+
panic!("ProposalV1 doesn't support Abstain vote")
762+
}
763+
764+
if self.veto_vote_weight.is_some() {
765+
panic!("ProposalV1 doesn't support Veto vote")
766+
}
767+
768+
if self.start_voting_at.is_some() {
769+
panic!("ProposalV1 doesn't support start time")
770+
}
771+
772+
if self.max_voting_time.is_some() {
773+
panic!("ProposalV1 doesn't support max voting time")
774+
}
775+
736776
let proposal_data_v1 = ProposalV1 {
737777
account_type: self.account_type,
738778
governance: self.governance,
@@ -837,6 +877,9 @@ pub fn get_proposal_data(
837877
transactions_next_index: proposal_data_v1.instructions_next_index,
838878
}],
839879
deny_vote_weight: Some(proposal_data_v1.no_votes_count),
880+
veto_vote_weight: None,
881+
abstain_vote_weight: None,
882+
start_voting_at: None,
840883
draft_at: proposal_data_v1.draft_at,
841884
signing_off_at: proposal_data_v1.signing_off_at,
842885
voting_at: proposal_data_v1.voting_at,
@@ -846,9 +889,11 @@ pub fn get_proposal_data(
846889
closed_at: proposal_data_v1.closed_at,
847890
execution_flags: proposal_data_v1.execution_flags,
848891
max_vote_weight: proposal_data_v1.max_vote_weight,
892+
max_voting_time: None,
849893
vote_threshold_percentage: proposal_data_v1.vote_threshold_percentage,
850894
name: proposal_data_v1.name,
851895
description_link: proposal_data_v1.description_link,
896+
reserved: [0; 8],
852897
});
853898
}
854899

@@ -925,17 +970,20 @@ pub fn assert_valid_proposal_options(
925970

926971
if let VoteType::MultiChoice {
927972
max_voter_options,
928-
max_executable_options,
973+
max_winning_options,
929974
} = *vote_type
930975
{
931976
if options.len() == 1
932977
|| max_voter_options as usize != options.len()
933-
|| max_executable_options as usize != options.len()
978+
|| max_winning_options as usize != options.len()
934979
{
935980
return Err(GovernanceError::InvalidProposalOptions.into());
936981
}
937982
}
938983

984+
// TODO: Check for duplicated option labels
985+
// The options are identified by index so it's ok for now
986+
939987
if options.iter().any(|o| o.is_empty()) {
940988
return Err(GovernanceError::InvalidProposalOptions.into());
941989
}
@@ -969,6 +1017,8 @@ mod test {
9691017
signatories_signed_off_count: 5,
9701018
description_link: "This is my description".to_string(),
9711019
name: "This is my name".to_string(),
1020+
1021+
start_voting_at: Some(0),
9721022
draft_at: 10,
9731023
signing_off_at: Some(10),
9741024

@@ -989,10 +1039,15 @@ mod test {
9891039
transactions_next_index: 10,
9901040
}],
9911041
deny_vote_weight: Some(0),
1042+
abstain_vote_weight: Some(0),
1043+
veto_vote_weight: Some(0),
9921044

9931045
execution_flags: InstructionExecutionFlags::Ordered,
9941046

1047+
max_voting_time: Some(0),
9951048
vote_threshold_percentage: Some(VoteThresholdPercentage::YesVote(100)),
1049+
1050+
reserved: [0; 8],
9961051
}
9971052
}
9981053

@@ -1066,7 +1121,7 @@ mod test {
10661121
let mut proposal = create_test_proposal();
10671122
proposal.vote_type = VoteType::MultiChoice {
10681123
max_voter_options: 1,
1069-
max_executable_options: 1,
1124+
max_winning_options: 1,
10701125
};
10711126

10721127
let size = proposal.try_to_vec().unwrap().len();
@@ -1079,7 +1134,7 @@ mod test {
10791134
let mut proposal = create_test_multi_option_proposal();
10801135
proposal.vote_type = VoteType::MultiChoice {
10811136
max_voter_options: 3,
1082-
max_executable_options: 3,
1137+
max_winning_options: 3,
10831138
};
10841139

10851140
let size = proposal.try_to_vec().unwrap().len();
@@ -2025,7 +2080,7 @@ mod test {
20252080
let mut proposal = create_test_multi_option_proposal();
20262081
proposal.vote_type = VoteType::MultiChoice {
20272082
max_voter_options: 3,
2028-
max_executable_options: 3,
2083+
max_winning_options: 3,
20292084
};
20302085

20312086
let choices = vec![
@@ -2061,7 +2116,7 @@ mod test {
20612116
// Arrange
20622117
let vote_type = VoteType::MultiChoice {
20632118
max_voter_options: 3,
2064-
max_executable_options: 3,
2119+
max_winning_options: 3,
20652120
};
20662121

20672122
let options = vec!["option 1".to_string(), "option 2".to_string()];
@@ -2078,7 +2133,7 @@ mod test {
20782133
// Arrange
20792134
let vote_type = VoteType::MultiChoice {
20802135
max_voter_options: 3,
2081-
max_executable_options: 3,
2136+
max_winning_options: 3,
20822137
};
20832138

20842139
let options = vec![];
@@ -2109,7 +2164,7 @@ mod test {
21092164
// Arrange
21102165
let vote_type = VoteType::MultiChoice {
21112166
max_voter_options: 3,
2112-
max_executable_options: 3,
2167+
max_winning_options: 3,
21132168
};
21142169

21152170
let options = vec![
@@ -2130,7 +2185,7 @@ mod test {
21302185
// Arrange
21312186
let vote_type = VoteType::MultiChoice {
21322187
max_voter_options: 3,
2133-
max_executable_options: 3,
2188+
max_winning_options: 3,
21342189
};
21352190

21362191
let options = vec![

governance/program/src/state/vote_record.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ pub enum Vote {
5151

5252
/// Vote rejecting proposal
5353
Deny,
54+
55+
/// Declare indifference to proposal
56+
/// Note: Not supported in the current version
57+
Abstain,
58+
59+
/// Veto proposal
60+
/// Note: Not supported in the current version
61+
Veto,
5462
}
5563

5664
/// Proposal VoteRecord
@@ -102,6 +110,9 @@ impl VoteRecordV2 {
102110
let vote_weight = match &self.vote {
103111
Vote::Approve(_options) => VoteWeightV1::Yes(self.voter_weight),
104112
Vote::Deny => VoteWeightV1::No(self.voter_weight),
113+
Vote::Abstain | Vote::Veto => {
114+
panic!("Vote type: {:?} not supported by VoteRecordV1", &self.vote)
115+
}
105116
};
106117

107118
let vote_record_data_v1 = VoteRecordV1 {

0 commit comments

Comments
 (0)