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

Commit bf7ad18

Browse files
Governance: vote options (#2544)
* feat: use VoteChoice instead of VoteWeight * chore: make clippy happy * feat: use options for ye/no vote * feat: use choices for CastVote instruction * chore: move Vote enum to tests * fix: iterate overall choices for withdrawal * chore: split ProposalOption and ProposalOptionVote * fix: calculate multi option proposal size * chore: split weighted and fractional vote choices * feat: add proposal type * feat: add reject option flag * feat: calculate final state for proposal using options results * chore: make clippy happy * fix: generalise max vote weight calculation for multiple options * feat: gate vote tipping for yes/no proposals only * chore: make clippy happy * feat: add option_index to instruction * feat: move instructions to options * chore: advance clock * chore: add await * chore: add multi option proposal tests * chore: move governing_mint to account list * feat: assert valid proposal options * feat: assert proposal is executable when instruction is added * chore: make clippy happy * chore: add tests to insert instructions into multi option proposal * chore: make clippy happy * feat: use explicit reject_option_vote_weight * feat: use Vote struct for vote results * feat: validate vote * feat: reject empty proposal options * chore: update comments * fix: allow execute only successful options * chore: add assertions for option statuses * chore: add partial success test * chore: add full success execution test * chore: add test for instructions execution for fully denied proposal * feat: finalise none executable proposals into completed state * chore: fix chat * feat: add vote_record v1 v2 roundtrip serialization * eat: add proposal_instruction v1 v2 roundtrip serialisation * chore: use VoteRecordV1 * chore: use legacy structs instead of legacy crate version * chore: rename proposal to V2 * feat: translate Proposal v1 v2 versions * chore: make clippy happy * chore: make clippy happy * chore: remove unnecessary clone for match * chore: rename get_final_vote_state to resolve_final_vote_state * fix proposal account name Co-authored-by: Jon Cinque <[email protected]> * chore: fix compilation * chore: use borsh::maybestd::io::Write * chore: consume self in serialise instructions to avoid cloning * chore: update comments * feat: add N limit placeholder to multi choice vote type * feat: increase options size to u16 * fix: use checked math Co-authored-by: Jon Cinque <[email protected]>
1 parent cd63580 commit bf7ad18

40 files changed

+2874
-447
lines changed

Cargo.lock

Lines changed: 3 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

governance/chat/program/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ serde = "1.0.127"
2121
serde_derive = "1.0.103"
2222
solana-program = "1.8.1"
2323
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] }
24-
spl-governance= { version = "2.1.2", path ="../../program", features = [ "no-entrypoint" ]}
24+
spl-governance= { version = "2.1.3", path ="../../program", features = [ "no-entrypoint" ]}
2525
spl-governance-tools= { version = "0.1.0", path ="../../tools"}
2626
thiserror = "1.0"
2727

governance/chat/program/src/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ pub struct ChatMessage {
5353

5454
impl AccountMaxSize for ChatMessage {
5555
fn get_max_size(&self) -> Option<usize> {
56-
let body_size = match self.body.clone() {
56+
let body_size = match &self.body {
5757
MessageBody::Text(body) => body.len(),
5858
MessageBody::Reaction(body) => body.len(),
5959
};

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use spl_governance::{
1111
state::{
1212
enums::{MintMaxVoteWeightSource, VoteThresholdPercentage},
1313
governance::{get_account_governance_address, GovernanceConfig},
14-
proposal::get_proposal_address,
14+
proposal::{get_proposal_address, VoteType},
1515
realm::get_realm_address,
1616
token_owner_record::get_token_owner_record_address,
1717
},
@@ -178,7 +178,9 @@ impl GovernanceChatProgramTest {
178178

179179
let proposal_name = "Proposal #1".to_string();
180180
let description_link = "Proposal Description".to_string();
181+
let options = vec!["Yes".to_string()];
181182
let proposal_index: u32 = 0;
183+
let use_deny_option = true;
182184

183185
let create_proposal_ix = create_proposal(
184186
&self.governance_program_id,
@@ -191,6 +193,9 @@ impl GovernanceChatProgramTest {
191193
proposal_name,
192194
description_link.clone(),
193195
&governing_token_mint_keypair.pubkey(),
196+
VoteType::SingleChoice,
197+
options,
198+
use_deny_option,
194199
proposal_index,
195200
);
196201

governance/program/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "spl-governance"
3-
version = "2.1.3"
3+
version = "2.1.4"
44
description = "Solana Program Library Governance Program"
55
authors = ["Solana Maintainers <[email protected]>"]
66
repository = "https://github.com/solana-labs/solana-program-library"
@@ -32,7 +32,7 @@ proptest = "1.0"
3232
solana-program-test = "1.8.1"
3333
solana-sdk = "1.8.1"
3434
spl-governance-test-sdk = { version = "0.1.0", path ="../test-sdk"}
35-
spl-governance-v1 = {package="spl-governance", version = "1.1.1", features = [ "no-entrypoint" ] }
35+
3636

3737
[lib]
3838
crate-type = ["cdylib", "lib"]

governance/program/src/error.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,30 @@ pub enum GovernanceError {
337337
/// Governing token deposits not allowed
338338
#[error("Governing token deposits not allowed")]
339339
GoverningTokenDepositsNotAllowed,
340+
341+
/// Invalid vote choice weight percentage
342+
#[error("Invalid vote choice weight percentage")]
343+
InvalidVoteChoiceWeightPercentage,
344+
345+
/// Vote type not supported
346+
#[error("Vote type not supported")]
347+
VoteTypeNotSupported,
348+
349+
/// InvalidProposalOptions
350+
#[error("Invalid proposal options")]
351+
InvalidProposalOptions,
352+
353+
/// Proposal is not not executable
354+
#[error("Proposal is not not executable")]
355+
ProposalIsNotExecutable,
356+
357+
/// Invalid vote
358+
#[error("Invalid vote")]
359+
InvalidVote,
360+
361+
/// Cannot execute defeated option
362+
#[error("Cannot execute defeated option")]
363+
CannotExecuteDefeatedOption,
340364
}
341365

342366
impl PrintProgramError for GovernanceError {

governance/program/src/instruction.rs

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ use crate::{
77
get_account_governance_address, get_mint_governance_address,
88
get_program_governance_address, get_token_governance_address, GovernanceConfig,
99
},
10-
proposal::get_proposal_address,
10+
proposal::{get_proposal_address, VoteType},
1111
proposal_instruction::{get_proposal_instruction_address, InstructionData},
1212
realm::{get_governing_token_holding_address, get_realm_address, RealmConfigArgs},
1313
realm_config::get_realm_config_address,
1414
signatory_record::get_signatory_record_address,
1515
token_owner_record::get_token_owner_record_address,
16-
vote_record::get_vote_record_address,
16+
vote_record::{get_vote_record_address, Vote},
1717
},
1818
tools::bpf_loader_upgradeable::get_program_data_address,
1919
};
@@ -25,16 +25,6 @@ use solana_program::{
2525
system_program, sysvar,
2626
};
2727

28-
/// Yes/No Vote
29-
#[repr(C)]
30-
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
31-
pub enum Vote {
32-
/// Yes vote
33-
Yes,
34-
/// No vote
35-
No,
36-
}
37-
3828
/// Instructions supported by the Governance program
3929
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
4030
#[repr(C)]
@@ -164,25 +154,36 @@ pub enum GovernanceInstruction {
164154
/// 1. `[writable]` Proposal account. PDA seeds ['governance',governance, governing_token_mint, proposal_index]
165155
/// 2. `[writable]` Governance account
166156
/// 3. `[writable]` TokenOwnerRecord account of the Proposal owner
167-
/// 4. `[signer]` Governance Authority (Token Owner or Governance Delegate)
168-
/// 5. `[signer]` Payer
169-
/// 6. `[]` System program
170-
/// 7. `[]` Rent sysvar
171-
/// 8. `[]` Clock sysvar
172-
/// 9. `[]` Optional Realm Config
173-
/// 10. `[]` Optional Voter Weight Record
157+
/// 4. `[]` Governing Token Mint the Proposal is created for
158+
/// 5. `[signer]` Governance Authority (Token Owner or Governance Delegate)
159+
/// 6. `[signer]` Payer
160+
/// 7. `[]` System program
161+
/// 8. `[]` Rent sysvar
162+
/// 9. `[]` Clock sysvar
163+
/// 10. `[]` Optional Realm Config
164+
/// 11. `[]` Optional Voter Weight Record
174165
CreateProposal {
175166
#[allow(dead_code)]
176167
/// UTF-8 encoded name of the proposal
177168
name: String,
178169

179170
#[allow(dead_code)]
180-
/// Link to gist explaining proposal
171+
/// Link to a gist explaining the proposal
181172
description_link: String,
182173

183174
#[allow(dead_code)]
184-
/// Governing Token Mint the Proposal is created for
185-
governing_token_mint: Pubkey,
175+
/// Proposal vote type
176+
vote_type: VoteType,
177+
178+
#[allow(dead_code)]
179+
/// Proposal options
180+
options: Vec<String>,
181+
182+
#[allow(dead_code)]
183+
/// Indicates whether the proposal has the deny option
184+
/// A proposal without the rejecting option is a non binding survey
185+
/// Only proposals with the rejecting option can have executable instructions
186+
use_deny_option: bool,
186187
},
187188

188189
/// Adds a signatory to the Proposal which means this Proposal can't leave Draft state until yet another Signatory signs
@@ -226,6 +227,9 @@ pub enum GovernanceInstruction {
226227
/// 6. `[]` System program
227228
/// 7. `[]` Rent sysvar
228229
InsertInstruction {
230+
#[allow(dead_code)]
231+
/// The index of the option the instruction is for
232+
option_index: u16,
229233
#[allow(dead_code)]
230234
/// Instruction index to be inserted at.
231235
index: u16,
@@ -285,7 +289,7 @@ pub enum GovernanceInstruction {
285289
/// 12. `[]` Optional Voter Weight Record
286290
CastVote {
287291
#[allow(dead_code)]
288-
/// Yes/No vote
292+
/// User's vote
289293
vote: Vote,
290294
},
291295

@@ -823,6 +827,9 @@ pub fn create_proposal(
823827
name: String,
824828
description_link: String,
825829
governing_token_mint: &Pubkey,
830+
vote_type: VoteType,
831+
options: Vec<String>,
832+
use_deny_option: bool,
826833
proposal_index: u32,
827834
) -> Instruction {
828835
let proposal_address = get_proposal_address(
@@ -837,6 +844,7 @@ pub fn create_proposal(
837844
AccountMeta::new(proposal_address, false),
838845
AccountMeta::new(*governance, false),
839846
AccountMeta::new(*proposal_owner_record, false),
847+
AccountMeta::new_readonly(*governing_token_mint, false),
840848
AccountMeta::new_readonly(*governance_authority, true),
841849
AccountMeta::new(*payer, true),
842850
AccountMeta::new_readonly(system_program::id(), false),
@@ -849,7 +857,9 @@ pub fn create_proposal(
849857
let instruction = GovernanceInstruction::CreateProposal {
850858
name,
851859
description_link,
852-
governing_token_mint: *governing_token_mint,
860+
vote_type,
861+
options,
862+
use_deny_option,
853863
};
854864

855865
Instruction {
@@ -1095,12 +1105,17 @@ pub fn insert_instruction(
10951105
governance_authority: &Pubkey,
10961106
payer: &Pubkey,
10971107
// Args
1108+
option_index: u16,
10981109
index: u16,
10991110
hold_up_time: u32,
11001111
instruction: InstructionData,
11011112
) -> Instruction {
1102-
let proposal_instruction_address =
1103-
get_proposal_instruction_address(program_id, proposal, &index.to_le_bytes());
1113+
let proposal_instruction_address = get_proposal_instruction_address(
1114+
program_id,
1115+
proposal,
1116+
&option_index.to_le_bytes(),
1117+
&index.to_le_bytes(),
1118+
);
11041119

11051120
let accounts = vec![
11061121
AccountMeta::new_readonly(*governance, false),
@@ -1114,6 +1129,7 @@ pub fn insert_instruction(
11141129
];
11151130

11161131
let instruction = GovernanceInstruction::InsertInstruction {
1132+
option_index,
11171133
index,
11181134
hold_up_time,
11191135
instruction,

governance/program/src/processor/mod.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,16 @@ pub fn process_instruction(
6868
try_from_slice_unchecked(input).map_err(|_| ProgramError::InvalidInstructionData)?;
6969

7070
if let GovernanceInstruction::InsertInstruction {
71+
option_index,
7172
index,
7273
hold_up_time,
7374
instruction: _,
7475
} = instruction
7576
{
7677
// Do not dump instruction data into logs
7778
msg!(
78-
"GOVERNANCE-INSTRUCTION: InsertInstruction {{ index: {:?}, hold_up_time: {:?} }}",
79+
"GOVERNANCE-INSTRUCTION: InsertInstruction {{option_index: {:?}, index: {:?}, hold_up_time: {:?} }}",
80+
option_index,
7981
index,
8082
hold_up_time
8183
);
@@ -127,13 +129,17 @@ pub fn process_instruction(
127129
GovernanceInstruction::CreateProposal {
128130
name,
129131
description_link,
130-
governing_token_mint,
132+
vote_type: proposal_type,
133+
options,
134+
use_deny_option,
131135
} => process_create_proposal(
132136
program_id,
133137
accounts,
134138
name,
135139
description_link,
136-
governing_token_mint,
140+
proposal_type,
141+
options,
142+
use_deny_option,
137143
),
138144
GovernanceInstruction::AddSignatory { signatory } => {
139145
process_add_signatory(program_id, accounts, signatory)
@@ -153,10 +159,18 @@ pub fn process_instruction(
153159
GovernanceInstruction::CancelProposal {} => process_cancel_proposal(program_id, accounts),
154160

155161
GovernanceInstruction::InsertInstruction {
162+
option_index,
156163
index,
157164
hold_up_time,
158165
instruction,
159-
} => process_insert_instruction(program_id, accounts, index, hold_up_time, instruction),
166+
} => process_insert_instruction(
167+
program_id,
168+
accounts,
169+
option_index,
170+
index,
171+
hold_up_time,
172+
instruction,
173+
),
160174

161175
GovernanceInstruction::RemoveInstruction {} => {
162176
process_remove_instruction(program_id, accounts)

governance/program/src/processor/process_add_signatory.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Program state processor
22
3-
use borsh::BorshSerialize;
43
use solana_program::{
54
account_info::{next_account_info, AccountInfo},
65
entrypoint::ProgramResult,

0 commit comments

Comments
 (0)