Skip to content

Commit

Permalink
Governance: vote options (solana-labs#2544)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
SebastianBor and joncinque authored Nov 11, 2021
1 parent cd63580 commit bf7ad18
Show file tree
Hide file tree
Showing 40 changed files with 2,874 additions and 447 deletions.
25 changes: 3 additions & 22 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion governance/chat/program/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ serde = "1.0.127"
serde_derive = "1.0.103"
solana-program = "1.8.1"
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] }
spl-governance= { version = "2.1.2", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance= { version = "2.1.3", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance-tools= { version = "0.1.0", path ="../../tools"}
thiserror = "1.0"

Expand Down
2 changes: 1 addition & 1 deletion governance/chat/program/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ pub struct ChatMessage {

impl AccountMaxSize for ChatMessage {
fn get_max_size(&self) -> Option<usize> {
let body_size = match self.body.clone() {
let body_size = match &self.body {
MessageBody::Text(body) => body.len(),
MessageBody::Reaction(body) => body.len(),
};
Expand Down
7 changes: 6 additions & 1 deletion governance/chat/program/tests/program_test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use spl_governance::{
state::{
enums::{MintMaxVoteWeightSource, VoteThresholdPercentage},
governance::{get_account_governance_address, GovernanceConfig},
proposal::get_proposal_address,
proposal::{get_proposal_address, VoteType},
realm::get_realm_address,
token_owner_record::get_token_owner_record_address,
},
Expand Down Expand Up @@ -178,7 +178,9 @@ impl GovernanceChatProgramTest {

let proposal_name = "Proposal #1".to_string();
let description_link = "Proposal Description".to_string();
let options = vec!["Yes".to_string()];
let proposal_index: u32 = 0;
let use_deny_option = true;

let create_proposal_ix = create_proposal(
&self.governance_program_id,
Expand All @@ -191,6 +193,9 @@ impl GovernanceChatProgramTest {
proposal_name,
description_link.clone(),
&governing_token_mint_keypair.pubkey(),
VoteType::SingleChoice,
options,
use_deny_option,
proposal_index,
);

Expand Down
4 changes: 2 additions & 2 deletions governance/program/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "spl-governance"
version = "2.1.3"
version = "2.1.4"
description = "Solana Program Library Governance Program"
authors = ["Solana Maintainers <[email protected]>"]
repository = "https://github.com/solana-labs/solana-program-library"
Expand Down Expand Up @@ -32,7 +32,7 @@ proptest = "1.0"
solana-program-test = "1.8.1"
solana-sdk = "1.8.1"
spl-governance-test-sdk = { version = "0.1.0", path ="../test-sdk"}
spl-governance-v1 = {package="spl-governance", version = "1.1.1", features = [ "no-entrypoint" ] }


[lib]
crate-type = ["cdylib", "lib"]
24 changes: 24 additions & 0 deletions governance/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,30 @@ pub enum GovernanceError {
/// Governing token deposits not allowed
#[error("Governing token deposits not allowed")]
GoverningTokenDepositsNotAllowed,

/// Invalid vote choice weight percentage
#[error("Invalid vote choice weight percentage")]
InvalidVoteChoiceWeightPercentage,

/// Vote type not supported
#[error("Vote type not supported")]
VoteTypeNotSupported,

/// InvalidProposalOptions
#[error("Invalid proposal options")]
InvalidProposalOptions,

/// Proposal is not not executable
#[error("Proposal is not not executable")]
ProposalIsNotExecutable,

/// Invalid vote
#[error("Invalid vote")]
InvalidVote,

/// Cannot execute defeated option
#[error("Cannot execute defeated option")]
CannotExecuteDefeatedOption,
}

impl PrintProgramError for GovernanceError {
Expand Down
68 changes: 42 additions & 26 deletions governance/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ use crate::{
get_account_governance_address, get_mint_governance_address,
get_program_governance_address, get_token_governance_address, GovernanceConfig,
},
proposal::get_proposal_address,
proposal::{get_proposal_address, VoteType},
proposal_instruction::{get_proposal_instruction_address, InstructionData},
realm::{get_governing_token_holding_address, get_realm_address, RealmConfigArgs},
realm_config::get_realm_config_address,
signatory_record::get_signatory_record_address,
token_owner_record::get_token_owner_record_address,
vote_record::get_vote_record_address,
vote_record::{get_vote_record_address, Vote},
},
tools::bpf_loader_upgradeable::get_program_data_address,
};
Expand All @@ -25,16 +25,6 @@ use solana_program::{
system_program, sysvar,
};

/// Yes/No Vote
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum Vote {
/// Yes vote
Yes,
/// No vote
No,
}

/// Instructions supported by the Governance program
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
#[repr(C)]
Expand Down Expand Up @@ -164,25 +154,36 @@ pub enum GovernanceInstruction {
/// 1. `[writable]` Proposal account. PDA seeds ['governance',governance, governing_token_mint, proposal_index]
/// 2. `[writable]` Governance account
/// 3. `[writable]` TokenOwnerRecord account of the Proposal owner
/// 4. `[signer]` Governance Authority (Token Owner or Governance Delegate)
/// 5. `[signer]` Payer
/// 6. `[]` System program
/// 7. `[]` Rent sysvar
/// 8. `[]` Clock sysvar
/// 9. `[]` Optional Realm Config
/// 10. `[]` Optional Voter Weight Record
/// 4. `[]` Governing Token Mint the Proposal is created for
/// 5. `[signer]` Governance Authority (Token Owner or Governance Delegate)
/// 6. `[signer]` Payer
/// 7. `[]` System program
/// 8. `[]` Rent sysvar
/// 9. `[]` Clock sysvar
/// 10. `[]` Optional Realm Config
/// 11. `[]` Optional Voter Weight Record
CreateProposal {
#[allow(dead_code)]
/// UTF-8 encoded name of the proposal
name: String,

#[allow(dead_code)]
/// Link to gist explaining proposal
/// Link to a gist explaining the proposal
description_link: String,

#[allow(dead_code)]
/// Governing Token Mint the Proposal is created for
governing_token_mint: Pubkey,
/// Proposal vote type
vote_type: VoteType,

#[allow(dead_code)]
/// Proposal options
options: Vec<String>,

#[allow(dead_code)]
/// Indicates whether the proposal has the deny option
/// A proposal without the rejecting option is a non binding survey
/// Only proposals with the rejecting option can have executable instructions
use_deny_option: bool,
},

/// Adds a signatory to the Proposal which means this Proposal can't leave Draft state until yet another Signatory signs
Expand Down Expand Up @@ -226,6 +227,9 @@ pub enum GovernanceInstruction {
/// 6. `[]` System program
/// 7. `[]` Rent sysvar
InsertInstruction {
#[allow(dead_code)]
/// The index of the option the instruction is for
option_index: u16,
#[allow(dead_code)]
/// Instruction index to be inserted at.
index: u16,
Expand Down Expand Up @@ -285,7 +289,7 @@ pub enum GovernanceInstruction {
/// 12. `[]` Optional Voter Weight Record
CastVote {
#[allow(dead_code)]
/// Yes/No vote
/// User's vote
vote: Vote,
},

Expand Down Expand Up @@ -823,6 +827,9 @@ pub fn create_proposal(
name: String,
description_link: String,
governing_token_mint: &Pubkey,
vote_type: VoteType,
options: Vec<String>,
use_deny_option: bool,
proposal_index: u32,
) -> Instruction {
let proposal_address = get_proposal_address(
Expand All @@ -837,6 +844,7 @@ pub fn create_proposal(
AccountMeta::new(proposal_address, false),
AccountMeta::new(*governance, false),
AccountMeta::new(*proposal_owner_record, false),
AccountMeta::new_readonly(*governing_token_mint, false),
AccountMeta::new_readonly(*governance_authority, true),
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(system_program::id(), false),
Expand All @@ -849,7 +857,9 @@ pub fn create_proposal(
let instruction = GovernanceInstruction::CreateProposal {
name,
description_link,
governing_token_mint: *governing_token_mint,
vote_type,
options,
use_deny_option,
};

Instruction {
Expand Down Expand Up @@ -1095,12 +1105,17 @@ pub fn insert_instruction(
governance_authority: &Pubkey,
payer: &Pubkey,
// Args
option_index: u16,
index: u16,
hold_up_time: u32,
instruction: InstructionData,
) -> Instruction {
let proposal_instruction_address =
get_proposal_instruction_address(program_id, proposal, &index.to_le_bytes());
let proposal_instruction_address = get_proposal_instruction_address(
program_id,
proposal,
&option_index.to_le_bytes(),
&index.to_le_bytes(),
);

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

let instruction = GovernanceInstruction::InsertInstruction {
option_index,
index,
hold_up_time,
instruction,
Expand Down
22 changes: 18 additions & 4 deletions governance/program/src/processor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,16 @@ pub fn process_instruction(
try_from_slice_unchecked(input).map_err(|_| ProgramError::InvalidInstructionData)?;

if let GovernanceInstruction::InsertInstruction {
option_index,
index,
hold_up_time,
instruction: _,
} = instruction
{
// Do not dump instruction data into logs
msg!(
"GOVERNANCE-INSTRUCTION: InsertInstruction {{ index: {:?}, hold_up_time: {:?} }}",
"GOVERNANCE-INSTRUCTION: InsertInstruction {{option_index: {:?}, index: {:?}, hold_up_time: {:?} }}",
option_index,
index,
hold_up_time
);
Expand Down Expand Up @@ -127,13 +129,17 @@ pub fn process_instruction(
GovernanceInstruction::CreateProposal {
name,
description_link,
governing_token_mint,
vote_type: proposal_type,
options,
use_deny_option,
} => process_create_proposal(
program_id,
accounts,
name,
description_link,
governing_token_mint,
proposal_type,
options,
use_deny_option,
),
GovernanceInstruction::AddSignatory { signatory } => {
process_add_signatory(program_id, accounts, signatory)
Expand All @@ -153,10 +159,18 @@ pub fn process_instruction(
GovernanceInstruction::CancelProposal {} => process_cancel_proposal(program_id, accounts),

GovernanceInstruction::InsertInstruction {
option_index,
index,
hold_up_time,
instruction,
} => process_insert_instruction(program_id, accounts, index, hold_up_time, instruction),
} => process_insert_instruction(
program_id,
accounts,
option_index,
index,
hold_up_time,
instruction,
),

GovernanceInstruction::RemoveInstruction {} => {
process_remove_instruction(program_id, accounts)
Expand Down
1 change: 0 additions & 1 deletion governance/program/src/processor/process_add_signatory.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//! Program state processor
use borsh::BorshSerialize;
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
Expand Down
Loading

0 comments on commit bf7ad18

Please sign in to comment.