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

Commit f59e437

Browse files
Governance: Voting workflow
Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
1 parent b8c2968 commit f59e437

39 files changed

Lines changed: 3215 additions & 312 deletions

governance/program/src/error.rs

Lines changed: 79 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,21 @@ pub enum GovernanceError {
3535
#[error("Governing Token Owner or Delegate must sign transaction")]
3636
GoverningTokenOwnerOrDelegateMustSign,
3737

38-
/// All active votes must be relinquished to withdraw governing tokens
39-
#[error("All active votes must be relinquished to withdraw governing tokens")]
40-
CannotWithdrawGoverningTokensWhenActiveVotesExist,
38+
/// All votes must be relinquished to withdraw governing tokens
39+
#[error("All votes must be relinquished to withdraw governing tokens")]
40+
AllVotesMustBeRelinquishedToWithdrawGoverningTokens,
4141

4242
/// Invalid Token Owner Record account address
4343
#[error("Invalid Token Owner Record account address")]
4444
InvalidTokenOwnerRecordAccountAddress,
4545

46-
/// Invalid Token Owner Record Governing mint
47-
#[error("Invalid Token Owner Record Governing mint")]
48-
InvalidTokenOwnerRecordGoverningMint,
46+
/// Invalid GoverningMint for TokenOwnerRecord
47+
#[error("Invalid GoverningMint for TokenOwnerRecord")]
48+
InvalidGoverningMintForTokenOwnerRecord,
4949

50-
/// Invalid Token Owner Record Realm
51-
#[error("Invalid Token Owner Record Realm")]
52-
InvalidTokenOwnerRecordRealm,
50+
/// Invalid Realm for TokenOwnerRecord
51+
#[error("Invalid Realm for TokenOwnerRecord")]
52+
InvalidRealmForTokenOwnerRecord,
5353

5454
/// Invalid Signatory account address
5555
#[error("Invalid Signatory account address")]
@@ -67,6 +67,14 @@ pub enum GovernanceError {
6767
#[error("Invalid Proposal Owner")]
6868
InvalidProposalOwnerAccount,
6969

70+
/// Invalid Proposal for VoterRecord
71+
#[error("Invalid Proposal for VoterRecord")]
72+
InvalidProposalForVoterRecord,
73+
74+
/// Invalid GoverningTokenOwner for VoteRecord
75+
#[error("Invalid GoverningTokenOwner for VoteRecord")]
76+
InvalidGoverningTokenOwnerForVoteRecord,
77+
7078
/// Invalid Governance config
7179
#[error("Invalid Governance config")]
7280
InvalidGovernanceConfig,
@@ -75,6 +83,10 @@ pub enum GovernanceError {
7583
#[error("Proposal for the given Governance, Governing Token Mint and index already exists")]
7684
ProposalAlreadyExists,
7785

86+
/// Token Owner already voted on the Proposal
87+
#[error("Token Owner already voted on the Proposal")]
88+
VoteAlreadyExists,
89+
7890
/// Owner doesn't have enough governing tokens to create Proposal
7991
#[error("Owner doesn't have enough governing tokens to create Proposal")]
8092
NotEnoughTokensToCreateProposal,
@@ -83,10 +95,38 @@ pub enum GovernanceError {
8395
#[error("Invalid State: Can't edit Signatories")]
8496
InvalidStateCannotEditSignatories,
8597

98+
/// Invalid Proposal state
99+
#[error("Invalid Proposal state")]
100+
InvalidProposalState,
101+
86102
/// Invalid State: Can't sign off
87103
#[error("Invalid State: Can't sign off")]
88104
InvalidStateCannotSignOff,
89105

106+
/// Invalid State: Can't vote
107+
#[error("Invalid State: Can't vote")]
108+
InvalidStateCannotVote,
109+
110+
/// Invalid State: Can't finalize vote
111+
#[error("Invalid State: Can't finalize vote")]
112+
InvalidStateCannotFinalize,
113+
114+
/// Invalid State: Can't cancel Proposal
115+
#[error("Invalid State: Can't cancel Proposal")]
116+
InvalidStateCannotCancelProposal,
117+
118+
/// Vote already relinquished
119+
#[error("Vote already relinquished")]
120+
VoteAlreadyRelinquished,
121+
122+
/// Can't finalize vote. Voting still in progress
123+
#[error("Can't finalize vote. Voting still in progress")]
124+
CannotFinalizeVotingInProgress,
125+
126+
/// Proposal voting time expired
127+
#[error("Proposal voting time expired")]
128+
ProposalVotingTimeExpired,
129+
90130
/// Invalid Signatory Mint
91131
#[error("Invalid Signatory Mint")]
92132
InvalidSignatoryMint,
@@ -101,11 +141,39 @@ pub enum GovernanceError {
101141
#[error("Invalid Account type")]
102142
InvalidAccountType,
103143

104-
/// ---- Token Tools Errors ----
144+
/// Proposal does not belong to the given Governance
145+
#[error("Proposal does not belong to the given Governance")]
146+
InvalidGovernanceForProposal,
147+
148+
/// Proposal does not belong to given Governing Mint"
149+
#[error("Proposal does not belong to given Governing Mint")]
150+
InvalidGoverningMintForProposal,
151+
152+
/// ---- SPL Token Tools Errors ----
105153
106154
/// Invalid Token account owner
107155
#[error("Invalid Token account owner")]
108-
InvalidTokenAccountOwner,
156+
SplTokenAccountWithInvalidOwner,
157+
158+
/// Invalid Mint account owner
159+
#[error("Invalid Mint account owner")]
160+
SplTokenMintWithInvalidOwner,
161+
162+
/// Token Account is not initialized
163+
#[error("Token Account is not initialized")]
164+
SplTokenAccountNotInitialized,
165+
166+
/// Token account data is invalid
167+
#[error("Token account data is invalid")]
168+
SplTokenInvalidTokenAccountData,
169+
170+
/// Token mint account data is invalid
171+
#[error("Token mint account data is invalid")]
172+
SplTokenInvalidMintAccountData,
173+
174+
/// Token Mint is not initialized
175+
#[error("Token Mint account is not initialized")]
176+
SplTokenMintNotInitialized,
109177

110178
/// ---- Bpf Upgradable Loader Tools Errors ----
111179

governance/program/src/instruction.rs

Lines changed: 149 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
signatory_record::get_signatory_record_address,
1212
single_signer_instruction::InstructionData,
1313
token_owner_record::get_token_owner_record_address,
14+
vote_record::get_vote_record_address,
1415
},
1516
tools::bpf_loader_upgradeable::get_program_data_address,
1617
};
@@ -178,7 +179,6 @@ pub enum GovernanceInstruction {
178179
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
179180
/// 3. `[writable]` Signatory Record Account
180181
/// 4. `[writable]` Beneficiary Account which would receive lamports from the disposed Signatory Record Account
181-
/// 5. `[]` Clock sysvar
182182
RemoveSignatory {
183183
#[allow(dead_code)]
184184
/// Signatory to remove from the Proposal
@@ -222,10 +222,11 @@ pub enum GovernanceInstruction {
222222
hold_up_time: u64,
223223
},
224224

225-
/// Cancels Proposal and moves it into Canceled
225+
/// Cancels Proposal by changing its state to Canceled
226226
///
227227
/// 0. `[writable]` Proposal account
228-
/// 1. `[signer]` Governance Authority (Token Owner or Governance Delegate)
228+
/// 1. `[]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
229+
/// 2 `[signer]` Governance Authority (Token Owner or Governance Delegate)
229230
CancelProposal,
230231

231232
/// Signs off Proposal indicating the Signatory approves the Proposal
@@ -241,26 +242,45 @@ pub enum GovernanceInstruction {
241242
/// By doing so you indicate you approve or disapprove of running the Proposal set of instructions
242243
/// If you tip the consensus then the instructions can begin to be run after their hold up time
243244
///
244-
/// 0. `[writable]` Proposal account
245-
/// 1. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
246-
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
247-
/// 3. `[signer]` Governance Authority account
248-
/// 4. `[]` Governance account
249-
Vote {
245+
/// 0. `[]` Governance account
246+
/// 1. `[writable]` Proposal account
247+
/// 2. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
248+
/// 3. `[signer]` Governance Authority (Token Owner or Governance Delegate)
249+
/// 4. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
250+
/// 5. `[]` Governing Token Mint
251+
/// 6. `[signer]` Payer
252+
/// 7. `[]` System program
253+
/// 8. `[]` Rent sysvar
254+
/// 9. `[]` Clock sysvar
255+
CastVote {
250256
#[allow(dead_code)]
251257
/// Yes/No vote
252258
vote: Vote,
253259
},
254260

261+
/// Finalizes vote in case the Vote was not automatically tipped within max_voting_time period
262+
///
263+
/// 0. `[]` Governance account
264+
/// 1. `[writable]` Proposal account
265+
/// 2. `[]` Governing Token Mint
266+
/// 3. `[]` Clock sysvar
267+
FinalizeVote {},
268+
255269
/// Relinquish Vote removes voter weight from a Proposal and removes it from voter's active votes
256270
/// If the Proposal is still being voted on then the voter's weight won't count towards the vote outcome
257271
/// If the Proposal is already in decided state then the instruction has no impact on the Proposal
258272
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
259273
///
260-
/// 0. `[writable]` Proposal account
261-
/// 1. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
262-
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
263-
/// 3. `[signer]` Governance Authority account
274+
/// 0. `[]` Governance account
275+
/// 1. `[writable]` Proposal account
276+
/// 2. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
277+
/// 3. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
278+
/// 4. `[]` Governing Token Mint
279+
280+
/// 5. `[signer]` Optional Governance Authority (Token Owner or Governance Delegate)
281+
/// It's required only when Proposal is still being voted on
282+
/// 6. `[writable]` Optional Beneficiary account which would receive lamports when VoteRecord Account is disposed
283+
/// It's required only when Proposal is still being voted on
264284
RelinquishVote,
265285

266286
/// Executes an instruction in the Proposal
@@ -494,7 +514,7 @@ pub fn create_proposal(
494514
name: String,
495515
description_link: String,
496516
governing_token_mint: &Pubkey,
497-
proposal_index: u16,
517+
proposal_index: u32,
498518
) -> Instruction {
499519
let proposal_address = get_proposal_address(
500520
governance,
@@ -578,7 +598,6 @@ pub fn remove_signatory(
578598
AccountMeta::new_readonly(*governance_authority, true),
579599
AccountMeta::new(signatory_record_address, false),
580600
AccountMeta::new(*beneficiary, false),
581-
AccountMeta::new_readonly(sysvar::clock::id(), false),
582601
];
583602

584603
let instruction = GovernanceInstruction::RemoveSignatory {
@@ -615,3 +634,118 @@ pub fn sign_off_proposal(
615634
data: instruction.try_to_vec().unwrap(),
616635
}
617636
}
637+
638+
/// Creates CastVote instruction
639+
pub fn cast_vote(
640+
// Accounts
641+
governance: &Pubkey,
642+
proposal: &Pubkey,
643+
token_owner_record: &Pubkey,
644+
governance_authority: &Pubkey,
645+
governing_token_mint: &Pubkey,
646+
payer: &Pubkey,
647+
// Args
648+
vote: Vote,
649+
) -> Instruction {
650+
let vote_record_address = get_vote_record_address(&proposal, &token_owner_record);
651+
652+
let accounts = vec![
653+
AccountMeta::new_readonly(*governance, false),
654+
AccountMeta::new(*proposal, false),
655+
AccountMeta::new(*token_owner_record, false),
656+
AccountMeta::new_readonly(*governance_authority, true),
657+
AccountMeta::new(vote_record_address, false),
658+
AccountMeta::new_readonly(*governing_token_mint, false),
659+
AccountMeta::new_readonly(*payer, true),
660+
AccountMeta::new_readonly(system_program::id(), false),
661+
AccountMeta::new_readonly(sysvar::rent::id(), false),
662+
AccountMeta::new_readonly(sysvar::clock::id(), false),
663+
];
664+
665+
let instruction = GovernanceInstruction::CastVote { vote };
666+
667+
Instruction {
668+
program_id: id(),
669+
accounts,
670+
data: instruction.try_to_vec().unwrap(),
671+
}
672+
}
673+
674+
/// Creates FinalizeVote instruction
675+
pub fn finalize_vote(
676+
// Accounts
677+
governance: &Pubkey,
678+
proposal: &Pubkey,
679+
governing_token_mint: &Pubkey,
680+
) -> Instruction {
681+
let accounts = vec![
682+
AccountMeta::new_readonly(*governance, false),
683+
AccountMeta::new(*proposal, false),
684+
AccountMeta::new_readonly(*governing_token_mint, false),
685+
AccountMeta::new_readonly(sysvar::clock::id(), false),
686+
];
687+
688+
let instruction = GovernanceInstruction::FinalizeVote {};
689+
690+
Instruction {
691+
program_id: id(),
692+
accounts,
693+
data: instruction.try_to_vec().unwrap(),
694+
}
695+
}
696+
697+
/// Creates RelinquishVote instruction
698+
pub fn relinquish_vote(
699+
// Accounts
700+
governance: &Pubkey,
701+
proposal: &Pubkey,
702+
token_owner_record: &Pubkey,
703+
governing_token_mint: &Pubkey,
704+
governance_authority: Option<Pubkey>,
705+
beneficiary: Option<Pubkey>,
706+
) -> Instruction {
707+
let vote_record_address = get_vote_record_address(&proposal, &token_owner_record);
708+
709+
let mut accounts = vec![
710+
AccountMeta::new_readonly(*governance, false),
711+
AccountMeta::new(*proposal, false),
712+
AccountMeta::new(*token_owner_record, false),
713+
AccountMeta::new(vote_record_address, false),
714+
AccountMeta::new_readonly(*governing_token_mint, false),
715+
];
716+
717+
if let Some(governance_authority) = governance_authority {
718+
accounts.push(AccountMeta::new_readonly(governance_authority, true));
719+
accounts.push(AccountMeta::new(beneficiary.unwrap(), false));
720+
}
721+
722+
let instruction = GovernanceInstruction::RelinquishVote {};
723+
724+
Instruction {
725+
program_id: id(),
726+
accounts,
727+
data: instruction.try_to_vec().unwrap(),
728+
}
729+
}
730+
731+
/// Creates CancelProposal instruction
732+
pub fn cancel_proposal(
733+
// Accounts
734+
proposal: &Pubkey,
735+
token_owner_record: &Pubkey,
736+
governance_authority: &Pubkey,
737+
) -> Instruction {
738+
let accounts = vec![
739+
AccountMeta::new(*proposal, false),
740+
AccountMeta::new_readonly(*token_owner_record, false),
741+
AccountMeta::new_readonly(*governance_authority, true),
742+
];
743+
744+
let instruction = GovernanceInstruction::CancelProposal {};
745+
746+
Instruction {
747+
program_id: id(),
748+
accounts,
749+
data: instruction.try_to_vec().unwrap(),
750+
}
751+
}

0 commit comments

Comments
 (0)