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

Commit 3d3b32d

Browse files
Governance: Veto vote (#3156)
* chore: remove #[repr(C)] * wip: resolve proposal governing token mint * fix: remove optionality from veto vote * feat: implement tipping for veto vote * fix: move vote_threshold to the end of Proposal struct * chore: remove use super * chore: make clippy happy * chore: add change log * feat: add council_veto_vote_threshold * fix: resolve vote threshold for voting token mint * chore: revert old function name * fix: calculate max veto in coerce_max_voter_weight * chore: make clippy happy * chore: make clippy happy * feat: Implement RelinquishVote for Veto * chore: update comments * chore: rename with_cast_vote to with_cast_yes_no_vote * chore: rename with_cast_multi_option_vote to with_cast_vote * chore: create use_veto_vote test scenario * chore: Add veto vote disabled tests * chore: Add partial Veto vote tests * chore: update comments * chore: test_cast_veto_vote_with_no_council_error * chore: test_relinquish_veto_vote * chore: rename with_token_owner_record to with_community_token_owner_record * chore: test_relinquish_veto_vote_with_vote_record_for_different_voting_mint_error * chore: test_cast_veto_vote_with_invalid_voting_mint_error * chore: fix chat build * chore: test_cast_veto_vote_with_council_only_allowed_to_veto * fix: Use VoteKind to distinguish between Veto and Electorate votes * chore: make clippy happy * Update change log Co-authored-by: Jon Cinque <[email protected]> * chore: rename voting_token_mint to vote_governing_token_mint * chore: test_cast_yes_and_veto_votes_with_yes_as_winning_vote * fix: throw error for Community veto * chore: Update comments * chore: Update names and commnents * chore: split try_get_tipped_vote_state into Electorate and Veto cases * chore: Update comments * chore: remove #[allow(clippy::float_cmp)] Co-authored-by: Jon Cinque <[email protected]>
1 parent 49c53ad commit 3d3b32d

36 files changed

+1406
-350
lines changed

governance/CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPL Governance Changelog
2+
3+
## v3.0.0 - development
4+
5+
- Support separate vote threshold for `Council`
6+
- `Council` Veto vote
7+
8+
## v2.2.4 - 24 Mar 2022
9+
10+
- Support Anchor native account discriminators for `MaxVoterWeightRecord` and `VoterWeightRecord`
11+
12+
## v2.2.3 - 09 Feb 2022
13+
14+
- Fix serialisation of multiple instructions within a single proposal transaction
15+
16+
## v2.2.2 - 07 Feb 2022
17+
18+
- Native SOL Treasuries
19+
- Multi choice and survey style proposals
20+
- `voter_weight` and `max_voter_weight` addins
21+
- Multiple instructions per proposal transaction
22+
- Configurable tipping point (`Strict`, `Early`, `Disabled`)
23+
- Owner signed off proposals
24+
- `realm_authority` can create governances
25+
- Program metadata and version detection
26+
- Custom deposit amount for governance tokens
27+
28+
## v1.1.1 - 23 Sep 2021
29+
30+
- Constrain number of outstanding proposals per token owner to 10 at a time
31+
32+
## v1.0.8 - 1 Aug 2021
33+
34+
- First release

governance/chat/program/src/state.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use solana_program::{
88
use spl_governance_tools::account::{assert_is_valid_account_of_type, AccountMaxSize};
99

1010
/// Defines all GovernanceChat accounts types
11-
#[repr(C)]
1211
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
1312
pub enum GovernanceChatAccountType {
1413
/// Default uninitialized account state

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ impl GovernanceChatProgramTest {
186186
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
187187
vote_tipping: spl_governance::state::enums::VoteTipping::Strict,
188188
council_vote_threshold: VoteThreshold::YesVotePercentage(10),
189-
reserved: [0; 2],
189+
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(50),
190190
};
191191

192192
let token_owner_record_address = get_token_owner_record_address(

governance/program/src/error.rs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub enum GovernanceError {
2525

2626
/// Invalid Governing Token Mint
2727
#[error("Invalid Governing Token Mint")]
28-
InvalidGoverningTokenMint,
28+
InvalidGoverningTokenMint, // 503
2929

3030
/// Governing Token Owner must sign transaction
3131
#[error("Governing Token Owner must sign transaction")]
@@ -45,11 +45,11 @@ pub enum GovernanceError {
4545

4646
/// Invalid GoverningMint for TokenOwnerRecord
4747
#[error("Invalid GoverningMint for TokenOwnerRecord")]
48-
InvalidGoverningMintForTokenOwnerRecord,
48+
InvalidGoverningMintForTokenOwnerRecord, // 508
4949

5050
/// Invalid Realm for TokenOwnerRecord
5151
#[error("Invalid Realm for TokenOwnerRecord")]
52-
InvalidRealmForTokenOwnerRecord,
52+
InvalidRealmForTokenOwnerRecord, // 509
5353

5454
/// Invalid Proposal for ProposalTransaction,
5555
#[error("Invalid Proposal for ProposalTransaction,")]
@@ -164,95 +164,95 @@ pub enum GovernanceError {
164164

165165
/// Proposal does not belong to the given Governance
166166
#[error("Proposal does not belong to the given Governance")]
167-
InvalidGovernanceForProposal,
167+
InvalidGovernanceForProposal, // 538
168168

169169
/// Proposal does not belong to given Governing Mint"
170170
#[error("Proposal does not belong to given Governing Mint")]
171-
InvalidGoverningMintForProposal,
171+
InvalidGoverningMintForProposal, // 539
172172

173173
/// Current mint authority must sign transaction
174174
#[error("Current mint authority must sign transaction")]
175-
MintAuthorityMustSign,
175+
MintAuthorityMustSign, // 540
176176

177177
/// Invalid mint authority
178178
#[error("Invalid mint authority")]
179-
InvalidMintAuthority,
179+
InvalidMintAuthority, // 542
180180

181181
/// Mint has no authority
182182
#[error("Mint has no authority")]
183-
MintHasNoAuthority,
183+
MintHasNoAuthority, // 542
184184

185185
/// ---- SPL Token Tools Errors ----
186186
187187
/// Invalid Token account owner
188188
#[error("Invalid Token account owner")]
189-
SplTokenAccountWithInvalidOwner,
189+
SplTokenAccountWithInvalidOwner, // 543
190190

191191
/// Invalid Mint account owner
192192
#[error("Invalid Mint account owner")]
193-
SplTokenMintWithInvalidOwner,
193+
SplTokenMintWithInvalidOwner, // 544
194194

195195
/// Token Account is not initialized
196196
#[error("Token Account is not initialized")]
197-
SplTokenAccountNotInitialized,
197+
SplTokenAccountNotInitialized, // 545
198198

199199
/// Token Account doesn't exist
200200
#[error("Token Account doesn't exist")]
201-
SplTokenAccountDoesNotExist,
201+
SplTokenAccountDoesNotExist, // 546
202202

203203
/// Token account data is invalid
204204
#[error("Token account data is invalid")]
205-
SplTokenInvalidTokenAccountData,
205+
SplTokenInvalidTokenAccountData, // 547
206206

207207
/// Token mint account data is invalid
208208
#[error("Token mint account data is invalid")]
209-
SplTokenInvalidMintAccountData,
209+
SplTokenInvalidMintAccountData, // 548
210210

211211
/// Token Mint is not initialized
212212
#[error("Token Mint account is not initialized")]
213-
SplTokenMintNotInitialized,
213+
SplTokenMintNotInitialized, // 549
214214

215215
/// Token Mint account doesn't exist
216216
#[error("Token Mint account doesn't exist")]
217-
SplTokenMintDoesNotExist,
217+
SplTokenMintDoesNotExist, // 550
218218

219219
/// ---- Bpf Upgradable Loader Tools Errors ----
220220
221221
/// Invalid ProgramData account Address
222222
#[error("Invalid ProgramData account address")]
223-
InvalidProgramDataAccountAddress,
223+
InvalidProgramDataAccountAddress, // 551
224224

225225
/// Invalid ProgramData account data
226226
#[error("Invalid ProgramData account Data")]
227-
InvalidProgramDataAccountData,
227+
InvalidProgramDataAccountData, // 552
228228

229229
/// Provided upgrade authority doesn't match current program upgrade authority
230230
#[error("Provided upgrade authority doesn't match current program upgrade authority")]
231-
InvalidUpgradeAuthority,
231+
InvalidUpgradeAuthority, // 553
232232

233233
/// Current program upgrade authority must sign transaction
234234
#[error("Current program upgrade authority must sign transaction")]
235-
UpgradeAuthorityMustSign,
235+
UpgradeAuthorityMustSign, // 554
236236

237237
/// Given program is not upgradable
238238
#[error("Given program is not upgradable")]
239-
ProgramNotUpgradable,
239+
ProgramNotUpgradable, // 555
240240

241241
/// Invalid token owner
242242
#[error("Invalid token owner")]
243-
InvalidTokenOwner,
243+
InvalidTokenOwner, // 556
244244

245245
/// Current token owner must sign transaction
246246
#[error("Current token owner must sign transaction")]
247-
TokenOwnerMustSign,
247+
TokenOwnerMustSign, // 557
248248

249249
/// Given VoteThresholdType is not supported
250250
#[error("Given VoteThresholdType is not supported")]
251-
VoteThresholdTypeNotSupported,
251+
VoteThresholdTypeNotSupported, // 558
252252

253253
/// Given VoteWeightSource is not supported
254254
#[error("Given VoteWeightSource is not supported")]
255-
VoteWeightSourceNotSupported,
255+
VoteWeightSourceNotSupported, // 559
256256

257257
/// GoverningTokenMint not allowed to vote
258258
#[error("GoverningTokenMint not allowed to vote")]

governance/program/src/instruction.rs

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ use solana_program::{
3030

3131
/// Instructions supported by the Governance program
3232
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
33-
#[repr(C)]
3433
#[allow(clippy::large_enum_variant)]
3534
pub enum GovernanceInstruction {
3635
/// Creates Governance Realm account which aggregates governances for given Community Mint and optional Council Mint
@@ -286,10 +285,14 @@ pub enum GovernanceInstruction {
286285
/// 1. `[writable]` Governance account
287286
/// 2. `[writable]` Proposal account
288287
/// 3. `[writable]` TokenOwnerRecord of the Proposal owner
289-
/// 4. `[writable]` TokenOwnerRecord of the voter. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
288+
/// 4. `[writable]` TokenOwnerRecord of the voter. PDA seeds: ['governance',realm, vote_governing_token_mint, governing_token_owner]
290289
/// 5. `[signer]` Governance Authority (Token Owner or Governance Delegate)
291-
/// 6. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
292-
/// 7. `[]` Governing Token Mint
290+
/// 6. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,token_owner_record]
291+
/// 7. `[]` The Governing Token Mint which is used to cast the vote (vote_governing_token_mint)
292+
/// The voting token mint is the governing_token_mint of the Proposal for Approve, Deny and Abstain votes
293+
/// For Veto vote the voting token mint is the mint of the opposite voting population
294+
/// Council mint to veto Community proposals and Community mint to veto Council proposals
295+
/// Note: In the current version only Council veto is supported
293296
/// 8. `[signer]` Payer
294297
/// 9. `[]` System program
295298
/// 10. `[]` Realm Config
@@ -317,14 +320,15 @@ pub enum GovernanceInstruction {
317320
/// If the Proposal is already in decided state then the instruction has no impact on the Proposal
318321
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
319322
///
320-
/// 0. `[]` Governance account
321-
/// 1. `[writable]` Proposal account
322-
/// 2. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
323-
/// 3. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
324-
/// 4. `[]` Governing Token Mint
325-
/// 5. `[signer]` Optional Governance Authority (Token Owner or Governance Delegate)
323+
/// 0. `[]` Realm account
324+
/// 1. `[]` Governance account
325+
/// 2. `[writable]` Proposal account
326+
/// 3. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, vote_governing_token_mint, governing_token_owner]
327+
/// 4. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal, token_owner_record]
328+
/// 5. `[]` The Governing Token Mint which was used to cast the vote (vote_governing_token_mint)
329+
/// 6. `[signer]` Optional Governance Authority (Token Owner or Governance Delegate)
326330
/// It's required only when Proposal is still being voted on
327-
/// 6. `[writable]` Optional Beneficiary account which would receive lamports when VoteRecord Account is disposed
331+
/// 7. `[writable]` Optional Beneficiary account which would receive lamports when VoteRecord Account is disposed
328332
/// It's required only when Proposal is still being voted on
329333
RelinquishVote,
330334

@@ -1020,7 +1024,7 @@ pub fn cast_vote(
10201024
proposal_owner_record: &Pubkey,
10211025
voter_token_owner_record: &Pubkey,
10221026
governance_authority: &Pubkey,
1023-
governing_token_mint: &Pubkey,
1027+
vote_governing_token_mint: &Pubkey,
10241028
payer: &Pubkey,
10251029
voter_weight_record: Option<Pubkey>,
10261030
max_voter_weight_record: Option<Pubkey>,
@@ -1038,7 +1042,7 @@ pub fn cast_vote(
10381042
AccountMeta::new(*voter_token_owner_record, false),
10391043
AccountMeta::new_readonly(*governance_authority, true),
10401044
AccountMeta::new(vote_record_address, false),
1041-
AccountMeta::new_readonly(*governing_token_mint, false),
1045+
AccountMeta::new_readonly(*vote_governing_token_mint, false),
10421046
AccountMeta::new(*payer, true),
10431047
AccountMeta::new_readonly(system_program::id(), false),
10441048
];
@@ -1097,24 +1101,27 @@ pub fn finalize_vote(
10971101
}
10981102

10991103
/// Creates RelinquishVote instruction
1104+
#[allow(clippy::too_many_arguments)]
11001105
pub fn relinquish_vote(
11011106
program_id: &Pubkey,
11021107
// Accounts
1108+
realm: &Pubkey,
11031109
governance: &Pubkey,
11041110
proposal: &Pubkey,
11051111
token_owner_record: &Pubkey,
1106-
governing_token_mint: &Pubkey,
1112+
vote_governing_token_mint: &Pubkey,
11071113
governance_authority: Option<Pubkey>,
11081114
beneficiary: Option<Pubkey>,
11091115
) -> Instruction {
11101116
let vote_record_address = get_vote_record_address(program_id, proposal, token_owner_record);
11111117

11121118
let mut accounts = vec![
1119+
AccountMeta::new_readonly(*realm, false),
11131120
AccountMeta::new_readonly(*governance, false),
11141121
AccountMeta::new(*proposal, false),
11151122
AccountMeta::new(*token_owner_record, false),
11161123
AccountMeta::new(vote_record_address, false),
1117-
AccountMeta::new_readonly(*governing_token_mint, false),
1124+
AccountMeta::new_readonly(*vote_governing_token_mint, false),
11181125
];
11191126

11201127
if let Some(governance_authority) = governance_authority {

governance/program/src/processor/process_cast_vote.rs

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::{
2222
get_token_owner_record_data_for_proposal_owner,
2323
get_token_owner_record_data_for_realm_and_governing_mint,
2424
},
25-
vote_record::{get_vote_record_address_seeds, Vote, VoteRecordV2},
25+
vote_record::{get_vote_kind, get_vote_record_address_seeds, Vote, VoteRecordV2},
2626
},
2727
};
2828

@@ -44,7 +44,7 @@ pub fn process_cast_vote(
4444
let governance_authority_info = next_account_info(account_info_iter)?; // 5
4545

4646
let vote_record_info = next_account_info(account_info_iter)?; // 6
47-
let governing_token_mint_info = next_account_info(account_info_iter)?; // 7
47+
let vote_governing_token_mint_info = next_account_info(account_info_iter)?; // 7
4848

4949
let payer_info = next_account_info(account_info_iter)?; // 8
5050
let system_info = next_account_info(account_info_iter)?; // 9
@@ -59,16 +59,27 @@ pub fn process_cast_vote(
5959
let mut realm_data = get_realm_data_for_governing_token_mint(
6060
program_id,
6161
realm_info,
62-
governing_token_mint_info.key,
62+
vote_governing_token_mint_info.key,
6363
)?;
64+
6465
let mut governance_data =
6566
get_governance_data_for_realm(program_id, governance_info, realm_info.key)?;
6667

68+
let vote_kind = get_vote_kind(&vote);
69+
70+
// Get the governing_token_mint which the Proposal should be configured with as the voting population for the given vote
71+
// For Approve, Deny and Abstain votes it's the same as vote_governing_token_mint
72+
// For Veto it's the governing token mint of the opposite voting population
73+
let proposal_governing_token_mint = realm_data.get_proposal_governing_token_mint_for_vote(
74+
vote_governing_token_mint_info.key,
75+
&vote_kind,
76+
)?;
77+
6778
let mut proposal_data = get_proposal_data_for_governance_and_governing_mint(
6879
program_id,
6980
proposal_info,
7081
governance_info.key,
71-
governing_token_mint_info.key,
82+
&proposal_governing_token_mint,
7283
)?;
7384
proposal_data.assert_can_cast_vote(&governance_data.config, clock.unix_timestamp)?;
7485

@@ -77,7 +88,7 @@ pub fn process_cast_vote(
7788
program_id,
7889
voter_token_owner_record_info,
7990
&governance_data.realm,
80-
governing_token_mint_info.key,
91+
vote_governing_token_mint_info.key,
8192
)?;
8293
voter_token_owner_record_data
8394
.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
@@ -129,28 +140,39 @@ pub fn process_cast_vote(
129140
.unwrap(),
130141
)
131142
}
132-
Vote::Abstain | Vote::Veto => {
143+
Vote::Veto => {
144+
proposal_data.veto_vote_weight = proposal_data
145+
.veto_vote_weight
146+
.checked_add(voter_weight)
147+
.unwrap();
148+
}
149+
Vote::Abstain => {
133150
return Err(GovernanceError::NotSupportedVoteType.into());
134151
}
135152
}
136153

137154
let max_voter_weight = proposal_data.resolve_max_voter_weight(
138155
program_id,
139156
realm_config_info,
140-
governing_token_mint_info,
157+
vote_governing_token_mint_info,
141158
account_info_iter, // max_voter_weight_record 11
142159
realm_info.key,
143160
&realm_data,
161+
&vote_kind,
144162
)?;
145163

146-
let vote_threshold =
147-
governance_data.resolve_vote_threshold(&realm_data, governing_token_mint_info.key)?;
164+
let vote_threshold = governance_data.resolve_vote_threshold(
165+
&realm_data,
166+
vote_governing_token_mint_info.key,
167+
&vote_kind,
168+
)?;
148169

149170
if proposal_data.try_tip_vote(
150171
max_voter_weight,
151-
&governance_data.config,
172+
&governance_data.config.vote_tipping,
152173
clock.unix_timestamp,
153174
&vote_threshold,
175+
&vote_kind,
154176
)? {
155177
// Deserialize proposal owner and validate it's the actual owner of the proposal
156178
let mut proposal_owner_record_data = get_token_owner_record_data_for_proposal_owner(

0 commit comments

Comments
 (0)