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 <jon.cinque@gmail.com>

* 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 <jon.cinque@gmail.com>
This commit is contained in:
Sebastian Bor 2022-05-17 16:37:43 +01:00 committed by GitHub
parent 49c53ad653
commit 3d3b32dcf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 1406 additions and 350 deletions

34
governance/CHANGELOG.md Normal file
View File

@ -0,0 +1,34 @@
# SPL Governance Changelog
## v3.0.0 - development
- Support separate vote threshold for `Council`
- `Council` Veto vote
## v2.2.4 - 24 Mar 2022
- Support Anchor native account discriminators for `MaxVoterWeightRecord` and `VoterWeightRecord`
## v2.2.3 - 09 Feb 2022
- Fix serialisation of multiple instructions within a single proposal transaction
## v2.2.2 - 07 Feb 2022
- Native SOL Treasuries
- Multi choice and survey style proposals
- `voter_weight` and `max_voter_weight` addins
- Multiple instructions per proposal transaction
- Configurable tipping point (`Strict`, `Early`, `Disabled`)
- Owner signed off proposals
- `realm_authority` can create governances
- Program metadata and version detection
- Custom deposit amount for governance tokens
## v1.1.1 - 23 Sep 2021
- Constrain number of outstanding proposals per token owner to 10 at a time
## v1.0.8 - 1 Aug 2021
- First release

View File

@ -8,7 +8,6 @@ use solana_program::{
use spl_governance_tools::account::{assert_is_valid_account_of_type, AccountMaxSize};
/// Defines all GovernanceChat accounts types
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum GovernanceChatAccountType {
/// Default uninitialized account state

View File

@ -186,7 +186,7 @@ impl GovernanceChatProgramTest {
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
vote_tipping: spl_governance::state::enums::VoteTipping::Strict,
council_vote_threshold: VoteThreshold::YesVotePercentage(10),
reserved: [0; 2],
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(50),
};
let token_owner_record_address = get_token_owner_record_address(

View File

@ -25,7 +25,7 @@ pub enum GovernanceError {
/// Invalid Governing Token Mint
#[error("Invalid Governing Token Mint")]
InvalidGoverningTokenMint,
InvalidGoverningTokenMint, // 503
/// Governing Token Owner must sign transaction
#[error("Governing Token Owner must sign transaction")]
@ -45,11 +45,11 @@ pub enum GovernanceError {
/// Invalid GoverningMint for TokenOwnerRecord
#[error("Invalid GoverningMint for TokenOwnerRecord")]
InvalidGoverningMintForTokenOwnerRecord,
InvalidGoverningMintForTokenOwnerRecord, // 508
/// Invalid Realm for TokenOwnerRecord
#[error("Invalid Realm for TokenOwnerRecord")]
InvalidRealmForTokenOwnerRecord,
InvalidRealmForTokenOwnerRecord, // 509
/// Invalid Proposal for ProposalTransaction,
#[error("Invalid Proposal for ProposalTransaction,")]
@ -164,95 +164,95 @@ pub enum GovernanceError {
/// Proposal does not belong to the given Governance
#[error("Proposal does not belong to the given Governance")]
InvalidGovernanceForProposal,
InvalidGovernanceForProposal, // 538
/// Proposal does not belong to given Governing Mint"
#[error("Proposal does not belong to given Governing Mint")]
InvalidGoverningMintForProposal,
InvalidGoverningMintForProposal, // 539
/// Current mint authority must sign transaction
#[error("Current mint authority must sign transaction")]
MintAuthorityMustSign,
MintAuthorityMustSign, // 540
/// Invalid mint authority
#[error("Invalid mint authority")]
InvalidMintAuthority,
InvalidMintAuthority, // 542
/// Mint has no authority
#[error("Mint has no authority")]
MintHasNoAuthority,
MintHasNoAuthority, // 542
/// ---- SPL Token Tools Errors ----
/// Invalid Token account owner
#[error("Invalid Token account owner")]
SplTokenAccountWithInvalidOwner,
SplTokenAccountWithInvalidOwner, // 543
/// Invalid Mint account owner
#[error("Invalid Mint account owner")]
SplTokenMintWithInvalidOwner,
SplTokenMintWithInvalidOwner, // 544
/// Token Account is not initialized
#[error("Token Account is not initialized")]
SplTokenAccountNotInitialized,
SplTokenAccountNotInitialized, // 545
/// Token Account doesn't exist
#[error("Token Account doesn't exist")]
SplTokenAccountDoesNotExist,
SplTokenAccountDoesNotExist, // 546
/// Token account data is invalid
#[error("Token account data is invalid")]
SplTokenInvalidTokenAccountData,
SplTokenInvalidTokenAccountData, // 547
/// Token mint account data is invalid
#[error("Token mint account data is invalid")]
SplTokenInvalidMintAccountData,
SplTokenInvalidMintAccountData, // 548
/// Token Mint is not initialized
#[error("Token Mint account is not initialized")]
SplTokenMintNotInitialized,
SplTokenMintNotInitialized, // 549
/// Token Mint account doesn't exist
#[error("Token Mint account doesn't exist")]
SplTokenMintDoesNotExist,
SplTokenMintDoesNotExist, // 550
/// ---- Bpf Upgradable Loader Tools Errors ----
/// Invalid ProgramData account Address
#[error("Invalid ProgramData account address")]
InvalidProgramDataAccountAddress,
InvalidProgramDataAccountAddress, // 551
/// Invalid ProgramData account data
#[error("Invalid ProgramData account Data")]
InvalidProgramDataAccountData,
InvalidProgramDataAccountData, // 552
/// Provided upgrade authority doesn't match current program upgrade authority
#[error("Provided upgrade authority doesn't match current program upgrade authority")]
InvalidUpgradeAuthority,
InvalidUpgradeAuthority, // 553
/// Current program upgrade authority must sign transaction
#[error("Current program upgrade authority must sign transaction")]
UpgradeAuthorityMustSign,
UpgradeAuthorityMustSign, // 554
/// Given program is not upgradable
#[error("Given program is not upgradable")]
ProgramNotUpgradable,
ProgramNotUpgradable, // 555
/// Invalid token owner
#[error("Invalid token owner")]
InvalidTokenOwner,
InvalidTokenOwner, // 556
/// Current token owner must sign transaction
#[error("Current token owner must sign transaction")]
TokenOwnerMustSign,
TokenOwnerMustSign, // 557
/// Given VoteThresholdType is not supported
#[error("Given VoteThresholdType is not supported")]
VoteThresholdTypeNotSupported,
VoteThresholdTypeNotSupported, // 558
/// Given VoteWeightSource is not supported
#[error("Given VoteWeightSource is not supported")]
VoteWeightSourceNotSupported,
VoteWeightSourceNotSupported, // 559
/// GoverningTokenMint not allowed to vote
#[error("GoverningTokenMint not allowed to vote")]

View File

@ -30,7 +30,6 @@ use solana_program::{
/// Instructions supported by the Governance program
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
#[repr(C)]
#[allow(clippy::large_enum_variant)]
pub enum GovernanceInstruction {
/// Creates Governance Realm account which aggregates governances for given Community Mint and optional Council Mint
@ -286,10 +285,14 @@ pub enum GovernanceInstruction {
/// 1. `[writable]` Governance account
/// 2. `[writable]` Proposal account
/// 3. `[writable]` TokenOwnerRecord of the Proposal owner
/// 4. `[writable]` TokenOwnerRecord of the voter. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
/// 4. `[writable]` TokenOwnerRecord of the voter. PDA seeds: ['governance',realm, vote_governing_token_mint, governing_token_owner]
/// 5. `[signer]` Governance Authority (Token Owner or Governance Delegate)
/// 6. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
/// 7. `[]` Governing Token Mint
/// 6. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,token_owner_record]
/// 7. `[]` The Governing Token Mint which is used to cast the vote (vote_governing_token_mint)
/// The voting token mint is the governing_token_mint of the Proposal for Approve, Deny and Abstain votes
/// For Veto vote the voting token mint is the mint of the opposite voting population
/// Council mint to veto Community proposals and Community mint to veto Council proposals
/// Note: In the current version only Council veto is supported
/// 8. `[signer]` Payer
/// 9. `[]` System program
/// 10. `[]` Realm Config
@ -317,14 +320,15 @@ pub enum GovernanceInstruction {
/// If the Proposal is already in decided state then the instruction has no impact on the Proposal
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
///
/// 0. `[]` Governance account
/// 1. `[writable]` Proposal account
/// 2. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
/// 3. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
/// 4. `[]` Governing Token Mint
/// 5. `[signer]` Optional Governance Authority (Token Owner or Governance Delegate)
/// 0. `[]` Realm account
/// 1. `[]` Governance account
/// 2. `[writable]` Proposal account
/// 3. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, vote_governing_token_mint, governing_token_owner]
/// 4. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal, token_owner_record]
/// 5. `[]` The Governing Token Mint which was used to cast the vote (vote_governing_token_mint)
/// 6. `[signer]` Optional Governance Authority (Token Owner or Governance Delegate)
/// It's required only when Proposal is still being voted on
/// 6. `[writable]` Optional Beneficiary account which would receive lamports when VoteRecord Account is disposed
/// 7. `[writable]` Optional Beneficiary account which would receive lamports when VoteRecord Account is disposed
/// It's required only when Proposal is still being voted on
RelinquishVote,
@ -1020,7 +1024,7 @@ pub fn cast_vote(
proposal_owner_record: &Pubkey,
voter_token_owner_record: &Pubkey,
governance_authority: &Pubkey,
governing_token_mint: &Pubkey,
vote_governing_token_mint: &Pubkey,
payer: &Pubkey,
voter_weight_record: Option<Pubkey>,
max_voter_weight_record: Option<Pubkey>,
@ -1038,7 +1042,7 @@ pub fn cast_vote(
AccountMeta::new(*voter_token_owner_record, false),
AccountMeta::new_readonly(*governance_authority, true),
AccountMeta::new(vote_record_address, false),
AccountMeta::new_readonly(*governing_token_mint, false),
AccountMeta::new_readonly(*vote_governing_token_mint, false),
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(system_program::id(), false),
];
@ -1097,24 +1101,27 @@ pub fn finalize_vote(
}
/// Creates RelinquishVote instruction
#[allow(clippy::too_many_arguments)]
pub fn relinquish_vote(
program_id: &Pubkey,
// Accounts
realm: &Pubkey,
governance: &Pubkey,
proposal: &Pubkey,
token_owner_record: &Pubkey,
governing_token_mint: &Pubkey,
vote_governing_token_mint: &Pubkey,
governance_authority: Option<Pubkey>,
beneficiary: Option<Pubkey>,
) -> Instruction {
let vote_record_address = get_vote_record_address(program_id, proposal, token_owner_record);
let mut accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(*governance, false),
AccountMeta::new(*proposal, false),
AccountMeta::new(*token_owner_record, false),
AccountMeta::new(vote_record_address, false),
AccountMeta::new_readonly(*governing_token_mint, false),
AccountMeta::new_readonly(*vote_governing_token_mint, false),
];
if let Some(governance_authority) = governance_authority {

View File

@ -22,7 +22,7 @@ use crate::{
get_token_owner_record_data_for_proposal_owner,
get_token_owner_record_data_for_realm_and_governing_mint,
},
vote_record::{get_vote_record_address_seeds, Vote, VoteRecordV2},
vote_record::{get_vote_kind, get_vote_record_address_seeds, Vote, VoteRecordV2},
},
};
@ -44,7 +44,7 @@ pub fn process_cast_vote(
let governance_authority_info = next_account_info(account_info_iter)?; // 5
let vote_record_info = next_account_info(account_info_iter)?; // 6
let governing_token_mint_info = next_account_info(account_info_iter)?; // 7
let vote_governing_token_mint_info = next_account_info(account_info_iter)?; // 7
let payer_info = next_account_info(account_info_iter)?; // 8
let system_info = next_account_info(account_info_iter)?; // 9
@ -59,16 +59,27 @@ pub fn process_cast_vote(
let mut realm_data = get_realm_data_for_governing_token_mint(
program_id,
realm_info,
governing_token_mint_info.key,
vote_governing_token_mint_info.key,
)?;
let mut governance_data =
get_governance_data_for_realm(program_id, governance_info, realm_info.key)?;
let vote_kind = get_vote_kind(&vote);
// Get the governing_token_mint which the Proposal should be configured with as the voting population for the given vote
// For Approve, Deny and Abstain votes it's the same as vote_governing_token_mint
// For Veto it's the governing token mint of the opposite voting population
let proposal_governing_token_mint = realm_data.get_proposal_governing_token_mint_for_vote(
vote_governing_token_mint_info.key,
&vote_kind,
)?;
let mut proposal_data = get_proposal_data_for_governance_and_governing_mint(
program_id,
proposal_info,
governance_info.key,
governing_token_mint_info.key,
&proposal_governing_token_mint,
)?;
proposal_data.assert_can_cast_vote(&governance_data.config, clock.unix_timestamp)?;
@ -77,7 +88,7 @@ pub fn process_cast_vote(
program_id,
voter_token_owner_record_info,
&governance_data.realm,
governing_token_mint_info.key,
vote_governing_token_mint_info.key,
)?;
voter_token_owner_record_data
.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
@ -129,7 +140,13 @@ pub fn process_cast_vote(
.unwrap(),
)
}
Vote::Abstain | Vote::Veto => {
Vote::Veto => {
proposal_data.veto_vote_weight = proposal_data
.veto_vote_weight
.checked_add(voter_weight)
.unwrap();
}
Vote::Abstain => {
return Err(GovernanceError::NotSupportedVoteType.into());
}
}
@ -137,20 +154,25 @@ pub fn process_cast_vote(
let max_voter_weight = proposal_data.resolve_max_voter_weight(
program_id,
realm_config_info,
governing_token_mint_info,
vote_governing_token_mint_info,
account_info_iter, // max_voter_weight_record 11
realm_info.key,
&realm_data,
&vote_kind,
)?;
let vote_threshold =
governance_data.resolve_vote_threshold(&realm_data, governing_token_mint_info.key)?;
let vote_threshold = governance_data.resolve_vote_threshold(
&realm_data,
vote_governing_token_mint_info.key,
&vote_kind,
)?;
if proposal_data.try_tip_vote(
max_voter_weight,
&governance_data.config,
&governance_data.config.vote_tipping,
clock.unix_timestamp,
&vote_threshold,
&vote_kind,
)? {
// Deserialize proposal owner and validate it's the actual owner of the proposal
let mut proposal_owner_record_data = get_token_owner_record_data_for_proposal_owner(

View File

@ -22,6 +22,7 @@ use crate::{
},
realm::get_realm_data_for_governing_token_mint,
token_owner_record::get_token_owner_record_data_for_realm,
vote_record::VoteKind,
},
};
@ -64,8 +65,11 @@ pub fn process_create_proposal(
let mut governance_data =
get_governance_data_for_realm(program_id, governance_info, realm_info.key)?;
governance_data
.assert_governing_token_mint_can_vote(&realm_data, governing_token_mint_info.key)?;
governance_data.assert_governing_token_mint_can_vote(
&realm_data,
governing_token_mint_info.key,
&VoteKind::Electorate,
)?;
let mut proposal_owner_record_data = get_token_owner_record_data_for_realm(
program_id,
@ -146,7 +150,7 @@ pub fn process_create_proposal(
options: proposal_options,
deny_vote_weight,
veto_vote_weight: None,
veto_vote_weight: 0,
abstain_vote_weight: None,
max_vote_weight: None,
@ -154,6 +158,7 @@ pub fn process_create_proposal(
vote_threshold: None,
reserved: [0; 64],
reserved1: 0,
};
create_and_serialize_account_signed::<ProposalV2>(

View File

@ -12,7 +12,7 @@ use crate::state::{
governance::get_governance_data_for_realm,
proposal::get_proposal_data_for_governance_and_governing_mint,
realm::get_realm_data_for_governing_token_mint,
token_owner_record::get_token_owner_record_data_for_proposal_owner,
token_owner_record::get_token_owner_record_data_for_proposal_owner, vote_record::VoteKind,
};
/// Processes FinalizeVote instruction
@ -52,10 +52,14 @@ pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> P
account_info_iter, // *6
realm_info.key,
&realm_data,
&VoteKind::Electorate,
)?;
let vote_threshold =
governance_data.resolve_vote_threshold(&realm_data, governing_token_mint_info.key)?;
let vote_threshold = governance_data.resolve_vote_threshold(
&realm_data,
governing_token_mint_info.key,
&VoteKind::Electorate,
)?;
proposal_data.finalize_vote(
max_voter_weight,

View File

@ -13,10 +13,11 @@ use crate::{
error::GovernanceError,
state::{
enums::ProposalState,
governance::get_governance_data,
proposal::get_proposal_data_for_governance_and_governing_mint,
governance::get_governance_data_for_realm,
proposal::get_proposal_data_for_governance,
realm::get_realm_data_for_governing_token_mint,
token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
vote_record::{get_vote_record_data_for_proposal_and_token_owner, Vote},
vote_record::{get_vote_record_data_for_proposal_and_token_owner_record, Vote},
},
};
@ -24,34 +25,40 @@ use crate::{
pub fn process_relinquish_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let governance_info = next_account_info(account_info_iter)?; // 0
let proposal_info = next_account_info(account_info_iter)?; // 1
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
let realm_info = next_account_info(account_info_iter)?; // 0
let governance_info = next_account_info(account_info_iter)?; // 1
let proposal_info = next_account_info(account_info_iter)?; // 2
let token_owner_record_info = next_account_info(account_info_iter)?; // 3
let vote_record_info = next_account_info(account_info_iter)?; // 3
let governing_token_mint_info = next_account_info(account_info_iter)?; // 4
let vote_record_info = next_account_info(account_info_iter)?; // 4
let vote_governing_token_mint_info = next_account_info(account_info_iter)?; // 5
let governance_data = get_governance_data(program_id, governance_info)?;
let mut proposal_data = get_proposal_data_for_governance_and_governing_mint(
let realm_data = get_realm_data_for_governing_token_mint(
program_id,
proposal_info,
governance_info.key,
governing_token_mint_info.key,
realm_info,
vote_governing_token_mint_info.key,
)?;
let governance_data =
get_governance_data_for_realm(program_id, governance_info, realm_info.key)?;
let mut proposal_data =
get_proposal_data_for_governance(program_id, proposal_info, governance_info.key)?;
let mut token_owner_record_data = get_token_owner_record_data_for_realm_and_governing_mint(
program_id,
token_owner_record_info,
&governance_data.realm,
governing_token_mint_info.key,
vote_governing_token_mint_info.key,
)?;
let mut vote_record_data = get_vote_record_data_for_proposal_and_token_owner(
let mut vote_record_data = get_vote_record_data_for_proposal_and_token_owner_record(
program_id,
vote_record_info,
&realm_data,
proposal_info.key,
&token_owner_record_data.governing_token_owner,
&proposal_data,
&token_owner_record_data,
)?;
vote_record_data.assert_can_relinquish_vote()?;
@ -89,7 +96,13 @@ pub fn process_relinquish_vote(program_id: &Pubkey, accounts: &[AccountInfo]) ->
.unwrap(),
)
}
Vote::Abstain | Vote::Veto => {
Vote::Veto => {
proposal_data.veto_vote_weight = proposal_data
.veto_vote_weight
.checked_sub(vote_record_data.voter_weight)
.unwrap();
}
Vote::Abstain => {
return Err(GovernanceError::NotSupportedVoteType.into());
}
}

View File

@ -3,7 +3,6 @@
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
/// Defines all Governance accounts types
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum GovernanceAccountType {
/// Default uninitialized account state
@ -97,7 +96,6 @@ impl Default for GovernanceAccountType {
}
/// What state a Proposal is in
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum ProposalState {
/// Draft - Proposal enters Draft state when it's created
@ -129,6 +127,9 @@ pub enum ProposalState {
/// Same as Executing but indicates some instructions failed to execute
/// Proposal can't be transitioned from ExecutingWithErrors to Completed state
ExecutingWithErrors,
/// The Proposal was vetoed
Vetoed,
}
impl Default for ProposalState {
@ -140,7 +141,6 @@ impl Default for ProposalState {
/// The type of the vote threshold used to resolve a vote on a Proposal
///
/// Note: In the current version only YesVotePercentage and Disabled thresholds are supported
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum VoteThreshold {
/// Voting threshold of Yes votes in % required to tip the vote (Approval Quorum)
@ -173,7 +173,6 @@ pub enum VoteThreshold {
/// The type of vote tipping to use on a Proposal.
///
/// Vote tipping means that under some conditions voting will complete early.
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum VoteTipping {
/// Tip when there is no way for another option to win and the vote threshold
@ -193,7 +192,6 @@ pub enum VoteTipping {
}
/// The status of instruction execution
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum TransactionExecutionStatus {
/// Transaction was not executed yet
@ -207,7 +205,6 @@ pub enum TransactionExecutionStatus {
}
/// Transaction execution flags defining how instructions are executed for a Proposal
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum InstructionExecutionFlags {
/// No execution flags are specified
@ -227,7 +224,6 @@ pub enum InstructionExecutionFlags {
/// The source of max vote weight used for voting
/// Values below 100% mint supply can be used when the governing token is fully minted but not distributed yet
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum MintMaxVoteWeightSource {
/// Fraction (10^10 precision) of the governing mint supply is used as max vote weight

View File

@ -7,6 +7,7 @@ use crate::{
enums::{GovernanceAccountType, VoteThreshold, VoteTipping},
legacy::{is_governance_v1_account_type, GovernanceV1},
realm::{assert_is_valid_realm, RealmV2},
vote_record::VoteKind,
},
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
@ -20,7 +21,6 @@ use spl_governance_tools::{
};
/// Governance config
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct GovernanceConfig {
/// The type of the vote threshold used for community vote
@ -43,15 +43,19 @@ pub struct GovernanceConfig {
/// Note: In the current version only YesVotePercentage and Disabled thresholds are supported
pub council_vote_threshold: VoteThreshold,
/// Reserved space for future versions
pub reserved: [u8; 2],
/// The threshold for Council Veto votes
pub council_veto_vote_threshold: VoteThreshold,
/// Minimum council weight a governance token owner must possess to be able to create a proposal
pub min_council_weight_to_create_proposal: u64,
//
// The threshold for Community Veto votes
// Note: Community Veto vote is not supported in the current version
// In order to use this threshold the space from GovernanceV2.reserved must be taken to expand GovernanceConfig size
// pub community_veto_vote_threshold: VoteThreshold,
}
/// Governance Account
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct GovernanceV2 {
/// Account type. It can be Uninitialized, Governance, ProgramGovernance, TokenGovernance or MintGovernance
@ -198,28 +202,39 @@ impl GovernanceV2 {
}
/// Asserts the provided voting population represented by the given governing_token_mint
/// can vote on proposals for the Governance
/// can cast the given vote type on proposals for the Governance
pub fn assert_governing_token_mint_can_vote(
&self,
realm_data: &RealmV2,
governing_token_mint: &Pubkey,
vote_governing_token_mint: &Pubkey,
vote_kind: &VoteKind,
) -> Result<(), ProgramError> {
// resolve_vote_threshold() asserts the vote threshold exists for the given governing_token_mint and is not disabled
let _ = self.resolve_vote_threshold(realm_data, governing_token_mint)?;
let _ = self.resolve_vote_threshold(realm_data, vote_governing_token_mint, vote_kind)?;
Ok(())
}
/// Resolves VoteThreshold for the given realm and governing token
/// Resolves VoteThreshold for the given realm, governing token and Vote kind
pub fn resolve_vote_threshold(
&self,
realm_data: &RealmV2,
governing_token_mint: &Pubkey,
vote_governing_token_mint: &Pubkey,
vote_kind: &VoteKind,
) -> Result<VoteThreshold, ProgramError> {
let vote_threshold = if realm_data.community_mint == *governing_token_mint {
&self.config.community_vote_threshold
} else if realm_data.config.council_mint == Some(*governing_token_mint) {
&self.config.council_vote_threshold
let vote_threshold = if realm_data.community_mint == *vote_governing_token_mint {
match vote_kind {
VoteKind::Electorate => &self.config.community_vote_threshold,
VoteKind::Veto => {
// Community Veto vote is not supported in current version
return Err(GovernanceError::GoverningTokenMintNotAllowedToVote.into());
}
}
} else if realm_data.config.council_mint == Some(*vote_governing_token_mint) {
match vote_kind {
VoteKind::Electorate => &self.config.council_vote_threshold,
VoteKind::Veto => &self.config.council_veto_vote_threshold,
}
} else {
return Err(GovernanceError::InvalidGoverningTokenMint.into());
};
@ -265,7 +280,7 @@ pub fn get_governance_data(
};
// In previous versions of spl-gov (< 3) we had config.proposal_cool_off_time:u32 which was unused and always 0
// In version 3.0.0 proposal_cool_off_time was replaced with council_vote_threshold:VoteThreshold
// In version 3.0.0 proposal_cool_off_time was replaced with council_vote_threshold:VoteThreshold and council_veto_vote_threshold:VoteThreshold
//
// If we read a legacy account then council_vote_threshold == VoteThreshold::YesVotePercentage(0)
// and we coerce it to be equal to community_vote_threshold which was used for both council and community thresholds before
@ -275,6 +290,10 @@ pub fn get_governance_data(
if governance_data.config.council_vote_threshold == VoteThreshold::YesVotePercentage(0) {
governance_data.config.council_vote_threshold =
governance_data.config.community_vote_threshold.clone();
// The assumption here is that council should have Veto vote enabled by default and equal to council_vote_threshold
governance_data.config.council_veto_vote_threshold =
governance_data.config.council_vote_threshold.clone();
}
Ok(governance_data)
@ -430,8 +449,9 @@ pub fn assert_is_valid_governance_config(
) -> Result<(), ProgramError> {
assert_is_valid_vote_threshold(&governance_config.community_vote_threshold)?;
assert_is_valid_vote_threshold(&governance_config.council_vote_threshold)?;
assert_is_valid_vote_threshold(&governance_config.council_veto_vote_threshold)?;
// Setting both thresholds to Disabled for now, however we might reconsider it as
// Setting both thresholds to Disabled is not allowed, however we might reconsider it as
// a way to disable Governance permanently
if governance_config.community_vote_threshold == VoteThreshold::Disabled
&& governance_config.council_vote_threshold == VoteThreshold::Disabled
@ -439,10 +459,6 @@ pub fn assert_is_valid_governance_config(
return Err(GovernanceError::AtLeastOneVoteThresholdRequired.into());
}
if governance_config.reserved != [0, 0] {
return Err(GovernanceError::ReservedBufferMustBeEmpty.into());
}
Ok(())
}
@ -470,12 +486,12 @@ mod test {
use super::*;
#[test]
fn test_deserialize_legacy_governance_account_without_council_threshold() {
fn test_deserialize_legacy_governance_account_without_council_vote_thresholds() {
// Arrange
// Legacy GovernanceV2 with
// 1) config.community_vote_threshold = YesVotePercentage(10)
// 2) config.proposal_cool_off_tim = 0
// 2) config.proposal_cool_off_time = 0
let mut account_data = [
18, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@ -514,6 +530,11 @@ mod test {
governance.config.community_vote_threshold,
governance.config.council_vote_threshold
);
assert_eq!(
governance.config.council_vote_threshold,
governance.config.council_veto_vote_threshold
);
}
#[test]
@ -526,7 +547,7 @@ mod test {
max_voting_time: 1,
vote_tipping: VoteTipping::Strict,
council_vote_threshold: VoteThreshold::YesVotePercentage(0),
reserved: [0; 2],
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
min_council_weight_to_create_proposal: 1,
};
@ -549,7 +570,7 @@ mod test {
max_voting_time: 1,
vote_tipping: VoteTipping::Strict,
council_vote_threshold: VoteThreshold::YesVotePercentage(1),
reserved: [0; 2],
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
min_council_weight_to_create_proposal: 1,
};
@ -572,7 +593,7 @@ mod test {
max_voting_time: 1,
vote_tipping: VoteTipping::Strict,
council_vote_threshold: VoteThreshold::Disabled,
reserved: [0; 2],
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(1),
min_council_weight_to_create_proposal: 1,
};
@ -586,16 +607,16 @@ mod test {
}
#[test]
fn test_assert_config_invalid_with_reserved_buffer_set() {
fn test_assert_config_invalid_with_council_zero_yes_veto_vote_threshold() {
// Arrange
let governance_config = GovernanceConfig {
community_vote_threshold: VoteThreshold::YesVotePercentage(10),
community_vote_threshold: VoteThreshold::YesVotePercentage(1),
min_community_weight_to_create_proposal: 1,
min_transaction_hold_up_time: 1,
max_voting_time: 1,
vote_tipping: VoteTipping::Strict,
council_vote_threshold: VoteThreshold::Disabled,
reserved: [0, 1],
council_vote_threshold: VoteThreshold::YesVotePercentage(1),
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(0),
min_council_weight_to_create_proposal: 1,
};
@ -605,6 +626,6 @@ mod test {
.unwrap();
// Assert
assert_eq!(err, GovernanceError::ReservedBufferMustBeEmpty.into());
assert_eq!(err, GovernanceError::InvalidVoteThresholdPercentage.into());
}
}

View File

@ -53,7 +53,6 @@ impl IsInitialized for RealmV1 {
/// Governance Token Owner Record
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct TokenOwnerRecordV1 {
/// Governance account type
@ -102,7 +101,6 @@ impl IsInitialized for TokenOwnerRecordV1 {
}
/// Governance Account
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct GovernanceV1 {
/// Account type. It can be Uninitialized, Governance, ProgramGovernance, TokenGovernance or MintGovernance
@ -173,7 +171,6 @@ impl IsInitialized for GovernanceV1 {
}
/// Governance Proposal
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct ProposalV1 {
/// Governance account type
@ -263,7 +260,6 @@ impl IsInitialized for ProposalV1 {
}
/// Account PDA seeds: ['governance', proposal, signatory]
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct SignatoryRecordV1 {
/// Governance account type

View File

@ -10,7 +10,6 @@ use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::state::enums::GovernanceAccountType;
/// Program metadata account. It stores information about the particular SPL-Governance program instance
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct ProgramMetadata {
/// Governance account type

View File

@ -32,6 +32,7 @@ use crate::{
realm::RealmV2,
realm_config::get_realm_config_data_for_realm,
vote_record::Vote,
vote_record::VoteKind,
},
PROGRAM_AUTHORITY_SEED,
};
@ -139,9 +140,9 @@ pub struct ProposalV2 {
/// Without the deny option a proposal is only non executable survey
pub deny_vote_weight: Option<u64>,
/// The total weight of Veto votes
/// Note: Veto is not supported in the current version
pub veto_vote_weight: Option<u64>,
/// Reserved space for future versions
/// This field is a leftover from unused veto_vote_weight: Option<u64>
pub reserved1: u8,
/// The total weight of votes
/// Note: Abstain is not supported in the current version
@ -200,6 +201,9 @@ pub struct ProposalV2 {
/// Link to proposal's description
pub description_link: String,
/// The total weight of Veto votes
pub veto_vote_weight: u64,
}
impl AccountMaxSize for ProposalV2 {
@ -232,7 +236,8 @@ impl ProposalV2 {
| ProposalState::Cancelled
| ProposalState::Voting
| ProposalState::Succeeded
| ProposalState::Defeated => Err(GovernanceError::InvalidStateCannotSignOff.into()),
| ProposalState::Defeated
| ProposalState::Vetoed => Err(GovernanceError::InvalidStateCannotSignOff.into()),
}
}
@ -414,10 +419,12 @@ impl ProposalV2 {
fn get_max_voter_weight_from_mint_supply(
&mut self,
realm_data: &RealmV2,
governing_token_mint: &Pubkey,
governing_token_mint_supply: u64,
vote_kind: &VoteKind,
) -> Result<u64, ProgramError> {
// max vote weight fraction is only used for community mint
if Some(self.governing_token_mint) == realm_data.config.council_mint {
if Some(*governing_token_mint) == realm_data.config.council_mint {
return Ok(governing_token_mint_supply);
}
@ -435,7 +442,7 @@ impl ProposalV2 {
// When the fraction is used it's possible we can go over the calculated max_vote_weight
// and we have to adjust it in case more votes have been cast
Ok(self.coerce_max_voter_weight(max_voter_weight))
Ok(self.coerce_max_voter_weight(max_voter_weight, vote_kind))
}
MintMaxVoteWeightSource::Absolute(_) => {
Err(GovernanceError::VoteWeightSourceNotSupported.into())
@ -444,61 +451,72 @@ impl ProposalV2 {
}
/// Adjusts max voter weight to ensure it's not lower than total cast votes
fn coerce_max_voter_weight(&self, max_voter_weight: u64) -> u64 {
let deny_vote_weight = self.deny_vote_weight.unwrap_or(0);
fn coerce_max_voter_weight(&self, max_voter_weight: u64, vote_kind: &VoteKind) -> u64 {
let total_vote_weight = match vote_kind {
VoteKind::Electorate => {
let deny_vote_weight = self.deny_vote_weight.unwrap_or(0);
let max_option_vote_weight = self.options.iter().map(|o| o.vote_weight).max().unwrap();
let max_option_vote_weight =
self.options.iter().map(|o| o.vote_weight).max().unwrap();
let total_vote_weight = max_option_vote_weight
.checked_add(deny_vote_weight)
.unwrap();
max_option_vote_weight
.checked_add(deny_vote_weight)
.unwrap()
}
VoteKind::Veto => self.veto_vote_weight,
};
max_voter_weight.max(total_vote_weight)
}
/// Resolves max voter weight
#[allow(clippy::too_many_arguments)]
pub fn resolve_max_voter_weight(
&mut self,
program_id: &Pubkey,
realm_config_info: &AccountInfo,
governing_token_mint_info: &AccountInfo,
vote_governing_token_mint_info: &AccountInfo,
account_info_iter: &mut Iter<AccountInfo>,
realm: &Pubkey,
realm_data: &RealmV2,
vote_kind: &VoteKind,
) -> Result<u64, ProgramError> {
// if the realm uses addin for max community voter weight then use the externally provided max weight
if realm_data.config.use_max_community_voter_weight_addin
&& realm_data.community_mint == self.governing_token_mint
&& realm_data.community_mint == *vote_governing_token_mint_info.key
{
let realm_config_data =
get_realm_config_data_for_realm(program_id, realm_config_info, realm)?;
let max_voter_weight_record_info = next_account_info(account_info_iter)?;
let max_voter_weight_data =
let max_voter_weight_record_data =
get_max_voter_weight_record_data_for_realm_and_governing_token_mint(
&realm_config_data.max_community_voter_weight_addin.unwrap(),
max_voter_weight_record_info,
realm,
&self.governing_token_mint,
vote_governing_token_mint_info.key,
)?;
assert_is_valid_max_voter_weight(&max_voter_weight_data)?;
assert_is_valid_max_voter_weight(&max_voter_weight_record_data)?;
// When the max voter weight addin is used it's possible it can be inaccurate and we can have more votes then the max provided by the addin
// and we have to adjust it to whatever result is higher
return Ok(self.coerce_max_voter_weight(max_voter_weight_data.max_voter_weight));
return Ok(self.coerce_max_voter_weight(
max_voter_weight_record_data.max_voter_weight,
vote_kind,
));
}
// Note: governing_token_mint_info is already verified at this point and this
// check is just a safety net in case some future refactoring would remove the validation
if self.governing_token_mint != *governing_token_mint_info.key {
return Err(GovernanceError::InvalidGoverningMintForProposal.into());
}
let governing_token_mint_supply = get_spl_token_mint_supply(governing_token_mint_info)?;
let vote_governing_token_mint_supply =
get_spl_token_mint_supply(vote_governing_token_mint_info)?;
let max_voter_weight =
self.get_max_voter_weight_from_mint_supply(realm_data, governing_token_mint_supply)?;
let max_voter_weight = self.get_max_voter_weight_from_mint_supply(
realm_data,
vote_governing_token_mint_info.key,
vote_governing_token_mint_supply,
vote_kind,
)?;
Ok(max_voter_weight)
}
@ -508,17 +526,22 @@ impl ProposalV2 {
pub fn try_tip_vote(
&mut self,
max_voter_weight: u64,
config: &GovernanceConfig,
vote_tipping: &VoteTipping,
current_unix_timestamp: UnixTimestamp,
vote_threshold: &VoteThreshold,
vote_kind: &VoteKind,
) -> Result<bool, ProgramError> {
if let Some(tipped_state) =
self.try_get_tipped_vote_state(max_voter_weight, config, vote_threshold)
{
if let Some(tipped_state) = self.try_get_tipped_vote_state(
max_voter_weight,
vote_tipping,
vote_threshold,
vote_kind,
) {
self.state = tipped_state;
self.voting_completed_at = Some(current_unix_timestamp);
// Capture vote params to correctly display historical results
// Note: For Veto vote the captured params are from the Veto config
self.max_vote_weight = Some(max_voter_weight);
self.vote_threshold = Some(vote_threshold.clone());
@ -528,22 +551,43 @@ impl ProposalV2 {
}
}
/// Checks if vote can be tipped and automatically transitioned to Succeeded or Defeated state
/// Checks if vote can be tipped and automatically transitioned to Succeeded, Defeated or Vetoed state
/// If yes then Some(ProposalState) is returned and None otherwise
#[allow(clippy::float_cmp)]
pub fn try_get_tipped_vote_state(
&mut self,
max_vote_weight: u64,
config: &GovernanceConfig,
max_voter_weight: u64,
vote_tipping: &VoteTipping,
vote_threshold: &VoteThreshold,
vote_kind: &VoteKind,
) -> Option<ProposalState> {
let min_vote_threshold_weight =
get_min_vote_threshold_weight(vote_threshold, max_voter_weight).unwrap();
match vote_kind {
VoteKind::Electorate => self.try_get_tipped_electorate_vote_state(
max_voter_weight,
vote_tipping,
min_vote_threshold_weight,
),
VoteKind::Veto => self.try_get_tipped_veto_vote_state(min_vote_threshold_weight),
}
}
/// Checks if Electorate vote can be tipped and automatically transitioned to Succeeded or Defeated state
/// If yes then Some(ProposalState) is returned and None otherwise
fn try_get_tipped_electorate_vote_state(
&mut self,
max_voter_weight: u64,
vote_tipping: &VoteTipping,
min_vote_threshold_weight: u64,
) -> Option<ProposalState> {
// Vote tipping is currently supported for SingleChoice votes with single Yes and No (rejection) options only
// Note: Tipping for multiple options (single choice and multiple choices) should be possible but it requires a great deal of considerations
// and I decided to fight it another day
if self.vote_type != VoteType::SingleChoice
// Tipping should not be allowed for opinion only proposals (surveys without rejection) to allow everybody's voice to be heard
|| self.deny_vote_weight.is_none()
|| self.options.len() != 1
// Tipping should not be allowed for opinion only proposals (surveys without rejection) to allow everybody's voice to be heard
|| self.deny_vote_weight.is_none()
|| self.options.len() != 1
{
return None;
};
@ -553,24 +597,21 @@ impl ProposalV2 {
let yes_vote_weight = yes_option.vote_weight;
let deny_vote_weight = self.deny_vote_weight.unwrap();
if yes_vote_weight == max_vote_weight {
if yes_vote_weight == max_voter_weight {
yes_option.vote_result = OptionVoteResult::Succeeded;
return Some(ProposalState::Succeeded);
}
if deny_vote_weight == max_vote_weight {
if deny_vote_weight == max_voter_weight {
yes_option.vote_result = OptionVoteResult::Defeated;
return Some(ProposalState::Defeated);
}
let min_vote_threshold_weight =
get_min_vote_threshold_weight(vote_threshold, max_vote_weight).unwrap();
match config.vote_tipping {
match vote_tipping {
VoteTipping::Disabled => {}
VoteTipping::Strict => {
if yes_vote_weight >= min_vote_threshold_weight
&& yes_vote_weight > (max_vote_weight.saturating_sub(yes_vote_weight))
&& yes_vote_weight > (max_voter_weight.saturating_sub(yes_vote_weight))
{
yes_option.vote_result = OptionVoteResult::Succeeded;
return Some(ProposalState::Succeeded);
@ -590,9 +631,9 @@ impl ProposalV2 {
// "defeated" if there is no possible way of reaching majority or the
// min_vote_threshold_weight for another option. This tipping is always
// strict, there's no equivalent to "early" tipping for deny votes.
if config.vote_tipping != VoteTipping::Disabled
&& (deny_vote_weight > (max_vote_weight.saturating_sub(min_vote_threshold_weight))
|| deny_vote_weight >= (max_vote_weight.saturating_sub(deny_vote_weight)))
if *vote_tipping != VoteTipping::Disabled
&& (deny_vote_weight > (max_voter_weight.saturating_sub(min_vote_threshold_weight))
|| deny_vote_weight >= (max_voter_weight.saturating_sub(deny_vote_weight)))
{
yes_option.vote_result = OptionVoteResult::Defeated;
return Some(ProposalState::Defeated);
@ -601,6 +642,22 @@ impl ProposalV2 {
None
}
/// Checks if vote can be tipped and transitioned to Vetoed state
/// If yes then Some(ProposalState::Vetoed) is returned and None otherwise
fn try_get_tipped_veto_vote_state(
&mut self,
min_vote_threshold_weight: u64,
) -> Option<ProposalState> {
// Veto vote tips as soon as the required threshold is reached
// It's irrespectively of vote_tipping config because the outcome of the Proposal can't change any longer after being vetoed
if self.veto_vote_weight >= min_vote_threshold_weight {
// Note: Since we don't tip multi option votes all options vote_result would remain as None
Some(ProposalState::Vetoed)
} else {
None
}
}
/// Checks if Proposal can be canceled in the given state
pub fn assert_can_cancel(
&self,
@ -622,7 +679,8 @@ impl ProposalV2 {
| ProposalState::Completed
| ProposalState::Cancelled
| ProposalState::Succeeded
| ProposalState::Defeated => {
| ProposalState::Defeated
| ProposalState::Vetoed => {
Err(GovernanceError::InvalidStateCannotCancelProposal.into())
}
}
@ -658,7 +716,8 @@ impl ProposalV2 {
| ProposalState::Completed
| ProposalState::Voting
| ProposalState::Cancelled
| ProposalState::Defeated => {
| ProposalState::Defeated
| ProposalState::Vetoed => {
return Err(GovernanceError::InvalidStateCannotExecuteTransaction.into())
}
}
@ -745,9 +804,10 @@ impl ProposalV2 {
return Err(GovernanceError::InvalidVote.into());
}
}
Vote::Abstain | Vote::Veto => {
Vote::Abstain => {
return Err(GovernanceError::NotSupportedVoteType.into());
}
Vote::Veto => {}
}
Ok(())
@ -764,7 +824,7 @@ impl ProposalV2 {
panic!("ProposalV1 doesn't support Abstain vote")
}
if self.veto_vote_weight.is_some() {
if self.veto_vote_weight > 0 {
panic!("ProposalV1 doesn't support Veto vote")
}
@ -818,7 +878,7 @@ impl ProposalV2 {
/// and returns the min weight required for a proposal option to pass
fn get_min_vote_threshold_weight(
vote_threshold: &VoteThreshold,
max_vote_weight: u64,
max_voter_weight: u64,
) -> Result<u64, ProgramError> {
let yes_vote_threshold_percentage = match vote_threshold {
VoteThreshold::YesVotePercentage(yes_vote_threshold_percentage) => {
@ -830,7 +890,7 @@ fn get_min_vote_threshold_weight(
};
let numerator = (yes_vote_threshold_percentage as u128)
.checked_mul(max_vote_weight as u128)
.checked_mul(max_voter_weight as u128)
.unwrap();
let mut yes_vote_threshold = numerator.checked_div(100).unwrap();
@ -863,7 +923,7 @@ pub fn get_proposal_data(
| ProposalState::Executing
| ProposalState::ExecutingWithErrors
| ProposalState::Completed => OptionVoteResult::Succeeded,
ProposalState::Defeated => OptionVoteResult::None,
ProposalState::Vetoed | ProposalState::Defeated => OptionVoteResult::None,
};
return Ok(ProposalV2 {
@ -884,7 +944,7 @@ pub fn get_proposal_data(
transactions_next_index: proposal_data_v1.instructions_next_index,
}],
deny_vote_weight: Some(proposal_data_v1.no_votes_count),
veto_vote_weight: None,
veto_vote_weight: 0,
abstain_vote_weight: None,
start_voting_at: None,
draft_at: proposal_data_v1.draft_at,
@ -901,13 +961,14 @@ pub fn get_proposal_data(
name: proposal_data_v1.name,
description_link: proposal_data_v1.description_link,
reserved: [0; 64],
reserved1: 0,
});
}
get_account_data::<ProposalV2>(program_id, proposal_info)
}
/// Deserializes Proposal and validates it belongs to the given Governance and Governing Mint
/// Deserializes Proposal and validates it belongs to the given Governance and governing_token_mint
pub fn get_proposal_data_for_governance_and_governing_mint(
program_id: &Pubkey,
proposal_info: &AccountInfo,
@ -1047,7 +1108,7 @@ mod test {
}],
deny_vote_weight: Some(0),
abstain_vote_weight: Some(0),
veto_vote_weight: Some(0),
veto_vote_weight: 0,
execution_flags: InstructionExecutionFlags::Ordered,
@ -1055,6 +1116,7 @@ mod test {
vote_threshold: Some(VoteThreshold::YesVotePercentage(100)),
reserved: [0; 64],
reserved1: 0,
}
}
@ -1122,7 +1184,7 @@ mod test {
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
vote_tipping: VoteTipping::Strict,
council_vote_threshold: VoteThreshold::YesVotePercentage(60),
reserved: [0; 2],
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(50),
}
}
@ -1186,6 +1248,7 @@ mod test {
Just(ProposalState::Completed),
Just(ProposalState::Cancelled),
Just(ProposalState::Defeated),
Just(ProposalState::Vetoed),
Just(ProposalState::SigningOff),
]
}
@ -1227,6 +1290,7 @@ mod test {
Just(ProposalState::Completed),
Just(ProposalState::Cancelled),
Just(ProposalState::Defeated),
Just(ProposalState::Vetoed),
]
}
@ -1279,6 +1343,7 @@ mod test {
Just(ProposalState::Completed),
Just(ProposalState::Cancelled),
Just(ProposalState::Defeated),
Just(ProposalState::Vetoed),
]
}
@ -1529,17 +1594,21 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_timestamp = 15_i64;
let realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let vote_tipping = VoteTipping::Strict;
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,test_case.governing_token_supply).unwrap();
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint, test_case.governing_token_supply,&vote_kind).unwrap();
let vote_threshold = VoteThreshold::YesVotePercentage(test_case.yes_vote_threshold_percentage);
// Act
proposal.try_tip_vote(max_voter_weight, &governance_config,current_timestamp,&vote_threshold).unwrap();
proposal.try_tip_vote(max_voter_weight, &vote_tipping,current_timestamp,&vote_threshold,&vote_kind).unwrap();
// Assert
assert_eq!(proposal.state,test_case.expected_tipped_state,"CASE: {:?}",test_case);
@ -1578,7 +1647,10 @@ mod test {
let current_timestamp = 16_i64;
let realm = create_test_realm();
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,test_case.governing_token_supply).unwrap();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint,test_case.governing_token_supply,&vote_kind).unwrap();
let vote_threshold = VoteThreshold::YesVotePercentage(test_case.yes_vote_threshold_percentage);
// Act
@ -1633,17 +1705,22 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let yes_vote_threshold_percentage = VoteThreshold::YesVotePercentage(yes_vote_threshold_percentage);
let current_timestamp = 15_i64;
let realm = create_test_realm();
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,governing_token_supply).unwrap();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let vote_tipping = VoteTipping::Strict;
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint,governing_token_supply,&vote_kind).unwrap();
let vote_threshold = yes_vote_threshold_percentage.clone();
// Act
proposal.try_tip_vote(max_voter_weight, &governance_config, current_timestamp,&vote_threshold).unwrap();
proposal.try_tip_vote(max_voter_weight, &vote_tipping, current_timestamp,&vote_threshold,&vote_kind).unwrap();
// Assert
let yes_vote_threshold_count = get_min_vote_threshold_weight(&yes_vote_threshold_percentage,governing_token_supply).unwrap();
@ -1684,7 +1761,10 @@ mod test {
let current_timestamp = 16_i64;
let realm = create_test_realm();
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,governing_token_supply).unwrap();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let max_voter_weight = proposal.get_max_voter_weight_from_mint_supply(&realm,&governing_token_mint,governing_token_supply,&vote_kind).unwrap();
let vote_threshold = yes_vote_threshold_percentage.clone();
// Act
@ -1714,12 +1794,14 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_timestamp = 15_i64;
let community_token_supply = 200;
let mut realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let vote_tipping = VoteTipping::Strict;
// reduce max vote weight to 100
realm.config.community_mint_max_vote_weight_source =
@ -1728,18 +1810,25 @@ mod test {
);
let max_voter_weight = proposal
.get_max_voter_weight_from_mint_supply(&realm, community_token_supply)
.get_max_voter_weight_from_mint_supply(
&realm,
&governing_token_mint,
community_token_supply,
&vote_kind,
)
.unwrap();
let vote_threshold = &VoteThreshold::YesVotePercentage(60);
let vote_kind = VoteKind::Electorate;
// Act
proposal
.try_tip_vote(
max_voter_weight,
&governance_config,
&vote_tipping,
current_timestamp,
vote_threshold,
&vote_kind,
)
.unwrap();
@ -1758,13 +1847,14 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_timestamp = 15_i64;
let community_token_supply = 200;
let mut realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let vote_tipping = VoteTipping::Strict;
// reduce max vote weight to 100
realm.config.community_mint_max_vote_weight_source =
@ -1777,7 +1867,12 @@ mod test {
proposal.options[0].vote_weight = 120;
let max_voter_weight = proposal
.get_max_voter_weight_from_mint_supply(&realm, community_token_supply)
.get_max_voter_weight_from_mint_supply(
&realm,
&governing_token_mint,
community_token_supply,
&vote_kind,
)
.unwrap();
let vote_threshold = VoteThreshold::YesVotePercentage(60);
@ -1786,9 +1881,10 @@ mod test {
proposal
.try_tip_vote(
max_voter_weight,
&governance_config,
&vote_tipping,
current_timestamp,
&vote_threshold,
&vote_kind,
)
.unwrap();
@ -1807,13 +1903,15 @@ mod test {
proposal.state = ProposalState::Voting;
let governance_config = create_test_governance_config();
let current_timestamp = 15_i64;
let community_token_supply = 200;
let mut realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let vote_tipping = VoteTipping::Strict;
realm.config.community_mint_max_vote_weight_source =
MintMaxVoteWeightSource::SupplyFraction(
MintMaxVoteWeightSource::SUPPLY_FRACTION_BASE / 2,
@ -1821,7 +1919,12 @@ mod test {
realm.config.council_mint = Some(proposal.governing_token_mint);
let max_voter_weight = proposal
.get_max_voter_weight_from_mint_supply(&realm, community_token_supply)
.get_max_voter_weight_from_mint_supply(
&realm,
&governing_token_mint,
community_token_supply,
&vote_kind,
)
.unwrap();
let vote_threshold = VoteThreshold::YesVotePercentage(60);
@ -1830,9 +1933,10 @@ mod test {
proposal
.try_tip_vote(
max_voter_weight,
&governance_config,
&vote_tipping,
current_timestamp,
&vote_threshold,
&vote_kind,
)
.unwrap();
@ -1856,6 +1960,8 @@ mod test {
let community_token_supply = 200;
let mut realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
// reduce max vote weight to 100
realm.config.community_mint_max_vote_weight_source =
@ -1864,7 +1970,12 @@ mod test {
);
let max_voter_weight = proposal
.get_max_voter_weight_from_mint_supply(&realm, community_token_supply)
.get_max_voter_weight_from_mint_supply(
&realm,
&governing_token_mint,
community_token_supply,
&vote_kind,
)
.unwrap();
let vote_threshold = VoteThreshold::YesVotePercentage(60);
@ -1900,6 +2011,8 @@ mod test {
let community_token_supply = 200;
let mut realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
// reduce max vote weight to 100
realm.config.community_mint_max_vote_weight_source =
@ -1911,7 +2024,12 @@ mod test {
proposal.options[0].vote_weight = 120;
let max_voter_weight = proposal
.get_max_voter_weight_from_mint_supply(&realm, community_token_supply)
.get_max_voter_weight_from_mint_supply(
&realm,
&governing_token_mint,
community_token_supply,
&vote_kind,
)
.unwrap();
let vote_threshold = VoteThreshold::YesVotePercentage(60);
@ -1942,8 +2060,11 @@ mod test {
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64;
let realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let max_voter_weight = proposal
.get_max_voter_weight_from_mint_supply(&realm, 100)
.get_max_voter_weight_from_mint_supply(&realm, &governing_token_mint, 100, &vote_kind)
.unwrap();
let vote_threshold = &governance_config.community_vote_threshold;
@ -1974,8 +2095,11 @@ mod test {
proposal.voting_at.unwrap() + governance_config.max_voting_time as i64 + 1;
let realm = create_test_realm();
let governing_token_mint = proposal.governing_token_mint;
let vote_kind = VoteKind::Electorate;
let max_voter_weight = proposal
.get_max_voter_weight_from_mint_supply(&realm, 100)
.get_max_voter_weight_from_mint_supply(&realm, &governing_token_mint, 100, &vote_kind)
.unwrap();
let vote_threshold = &governance_config.community_vote_threshold;

View File

@ -26,7 +26,6 @@ use spl_governance_tools::account::{get_account_data, AccountMaxSize};
/// InstructionData wrapper. It can be removed once Borsh serialization for Instruction is supported in the SDK
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
#[repr(C)]
pub struct InstructionData {
/// Pubkey of the instruction processor that executes this instruction
pub program_id: Pubkey,
@ -38,7 +37,6 @@ pub struct InstructionData {
/// Account metadata used to define Instructions
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
#[repr(C)]
pub struct AccountMetaData {
/// An account's public key
pub pubkey: Pubkey,
@ -85,7 +83,6 @@ impl From<&InstructionData> for Instruction {
}
/// Account for an instruction to be executed for Proposal
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct ProposalTransactionV2 {
/// Governance Account type

View File

@ -22,12 +22,12 @@ use crate::{
enums::{GovernanceAccountType, MintMaxVoteWeightSource},
legacy::RealmV1,
token_owner_record::get_token_owner_record_data_for_realm,
vote_record::VoteKind,
},
PROGRAM_AUTHORITY_SEED,
};
/// Realm Config instruction args
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfigArgs {
/// Indicates whether council_mint should be used
@ -66,7 +66,6 @@ pub enum SetRealmAuthorityAction {
}
/// Realm Config defining Realm parameters.
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfig {
/// Indicates whether an external addin program should be used to provide voters weights for the community mint
@ -90,7 +89,6 @@ pub struct RealmConfig {
/// Governance Realm Account
/// Account PDA seeds" ['governance', name]
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmV2 {
/// Governance account type
@ -177,6 +175,37 @@ impl RealmV2 {
Err(GovernanceError::InvalidGoverningTokenMint.into())
}
/// Returns the governing token mint which is used to vote on a proposal given the provided Vote kind and vote_governing_token_mint
///
/// Veto vote is cast on a proposal configured for the opposite voting population defined using governing_token_mint
/// Council can veto Community vote and Community can veto Council assuming the veto for the voting population is enabled
///
/// For all votes other than Veto (Electorate votes) the vote_governing_token_mint is the same as Proposal governing_token_mint
pub fn get_proposal_governing_token_mint_for_vote(
&self,
vote_governing_token_mint: &Pubkey,
vote_kind: &VoteKind,
) -> Result<Pubkey, ProgramError> {
match vote_kind {
VoteKind::Electorate => Ok(*vote_governing_token_mint),
VoteKind::Veto => {
// When Community veto Council proposal then return council_token_mint as the Proposal governing_token_mint
if self.community_mint == *vote_governing_token_mint {
// Community Veto is not supported in the current version
return Err(GovernanceError::GoverningTokenMintNotAllowedToVote.into());
//return Ok(self.config.council_mint.unwrap());
}
// When Council veto Community proposal then return community_token_mint as the Proposal governing_token_mint
if self.config.council_mint == Some(*vote_governing_token_mint) {
return Ok(self.community_mint);
}
Err(GovernanceError::InvalidGoverningTokenMint.into())
}
}
}
/// Asserts the given governing token mint and holding accounts are valid for the realm
pub fn assert_is_valid_governing_token_mint_and_holding(
&self,

View File

@ -12,12 +12,9 @@ use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::{error::GovernanceError, PROGRAM_AUTHORITY_SEED};
use crate::state::enums::GovernanceAccountType;
use super::legacy::SignatoryRecordV1;
use crate::state::{enums::GovernanceAccountType, legacy::SignatoryRecordV1};
/// Account PDA seeds: ['governance', proposal, signatory]
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct SignatoryRecordV2 {
/// Governance account type

View File

@ -28,7 +28,6 @@ use spl_governance_tools::account::{get_account_data, AccountMaxSize};
/// Governance Token Owner Record
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct TokenOwnerRecordV2 {
/// Governance account type

View File

@ -17,6 +17,9 @@ use crate::PROGRAM_AUTHORITY_SEED;
use crate::state::{
enums::GovernanceAccountType,
legacy::{VoteRecordV1, VoteWeightV1},
proposal::ProposalV2,
realm::RealmV2,
token_owner_record::TokenOwnerRecordV2,
};
/// Voter choice for a proposal option
@ -57,10 +60,28 @@ pub enum Vote {
Abstain,
/// Veto proposal
/// Note: Not supported in the current version
Veto,
}
/// VoteKind defines the type of the vote being cast
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum VoteKind {
/// Electorate vote is cast by the voting population identified by governing_token_mint
/// Approve, Deny and Abstain votes are Electorate votes
Electorate,
/// Vote cast by the opposite voting population to the Electorate identified by governing_token_mint
Veto,
}
/// Returns the VoteKind for the given Vote
pub fn get_vote_kind(vote: &Vote) -> VoteKind {
match vote {
Vote::Approve(_) | Vote::Deny | Vote::Abstain => VoteKind::Electorate,
Vote::Veto => VoteKind::Veto,
}
}
/// Proposal VoteRecord
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct VoteRecordV2 {
@ -177,12 +198,14 @@ pub fn get_vote_record_data(
get_account_data::<VoteRecordV2>(program_id, vote_record_info)
}
/// Deserializes VoteRecord and checks it belongs to the provided Proposal and Governing Token Owner
pub fn get_vote_record_data_for_proposal_and_token_owner(
/// Deserializes VoteRecord and checks it belongs to the provided Proposal and TokenOwnerRecord
pub fn get_vote_record_data_for_proposal_and_token_owner_record(
program_id: &Pubkey,
vote_record_info: &AccountInfo,
realm_data: &RealmV2,
proposal: &Pubkey,
governing_token_owner: &Pubkey,
proposal_data: &ProposalV2,
token_owner_record_data: &TokenOwnerRecordV2,
) -> Result<VoteRecordV2, ProgramError> {
let vote_record_data = get_vote_record_data(program_id, vote_record_info)?;
@ -190,10 +213,22 @@ pub fn get_vote_record_data_for_proposal_and_token_owner(
return Err(GovernanceError::InvalidProposalForVoterRecord.into());
}
if vote_record_data.governing_token_owner != *governing_token_owner {
if vote_record_data.governing_token_owner != token_owner_record_data.governing_token_owner {
return Err(GovernanceError::InvalidGoverningTokenOwnerForVoteRecord.into());
}
// Assert governing_token_mint between Proposal and TokenOwnerRecord match for the deserialized VoteRecord
// For Approve, Deny and Abstain votes Proposal.governing_token_mint must equal TokenOwnerRecord.governing_token_mint
// For Veto vote it must be the governing_token_mint of the opposite voting population
let proposal_governing_token_mint = realm_data.get_proposal_governing_token_mint_for_vote(
&token_owner_record_data.governing_token_mint,
&get_vote_kind(&vote_record_data.vote),
)?;
if proposal_data.governing_token_mint != proposal_governing_token_mint {
return Err(GovernanceError::InvalidGoverningMintForProposal.into());
}
Ok(vote_record_data)
}

View File

@ -97,7 +97,7 @@ async fn test_cancel_proposal_with_already_completed_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -42,7 +42,7 @@ async fn test_cast_vote() {
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -139,7 +139,7 @@ async fn test_cast_vote_with_invalid_governance_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -155,7 +155,7 @@ async fn test_cast_vote_with_invalid_mint_error() {
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
@ -169,18 +169,18 @@ async fn test_cast_vote_with_invalid_mint_error() {
.await
.unwrap();
let mut proposal_cookie = governance_test
let proposal_cookie = governance_test
.with_signed_off_proposal(&token_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Try to use Council Mint with Community Proposal
proposal_cookie.account.governing_token_mint =
token_owner_record_cookie.account.governing_token_mint =
realm_cookie.account.config.council_mint.unwrap();
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -225,7 +225,7 @@ async fn test_cast_vote_with_invalid_token_owner_record_mint_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -275,7 +275,7 @@ async fn test_cast_vote_with_invalid_token_owner_record_from_different_realm_err
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -320,7 +320,7 @@ async fn test_cast_vote_with_governance_authority_must_sign_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -374,7 +374,7 @@ async fn test_cast_vote_with_strict_vote_tipped_to_succeeded() {
// Act
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie1,
YesNoVote::Yes,
@ -392,7 +392,7 @@ async fn test_cast_vote_with_strict_vote_tipped_to_succeeded() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.await
.unwrap();
@ -406,7 +406,7 @@ async fn test_cast_vote_with_strict_vote_tipped_to_succeeded() {
// Act
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie3,
YesNoVote::Yes,
@ -488,7 +488,7 @@ async fn test_cast_vote_with_strict_vote_tipped_to_defeated() {
// Act
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie1,
YesNoVote::Yes,
@ -506,7 +506,7 @@ async fn test_cast_vote_with_strict_vote_tipped_to_defeated() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.await
.unwrap();
@ -520,7 +520,7 @@ async fn test_cast_vote_with_strict_vote_tipped_to_defeated() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie3, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie3, YesNoVote::No)
.await
.unwrap();
@ -597,7 +597,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
.await
.unwrap();
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie1,
YesNoVote::Yes,
@ -610,7 +610,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
assert_eq!(ProposalState::Voting, proposal_account.state);
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.await
.unwrap();
let proposal_account = governance_test
@ -619,7 +619,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
assert_eq!(ProposalState::Voting, proposal_account.state);
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie3,
YesNoVote::Yes,
@ -641,7 +641,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
.await
.unwrap();
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie1,
YesNoVote::Yes,
@ -654,7 +654,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
assert_eq!(ProposalState::Voting, proposal_account.state);
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.await
.unwrap();
let proposal_account = governance_test
@ -663,7 +663,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
assert_eq!(ProposalState::Voting, proposal_account.state);
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie3, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie3, YesNoVote::No)
.await
.unwrap();
let proposal_account = governance_test
@ -672,7 +672,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
assert_eq!(ProposalState::Voting, proposal_account.state);
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie4,
YesNoVote::Yes,
@ -686,7 +686,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_succeeded() {
// Act: 300 vs 200 makes it tip
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie5,
YesNoVote::Yes,
@ -756,7 +756,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_defeated() {
// Act
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie1,
YesNoVote::Yes,
@ -774,7 +774,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_defeated() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie2, YesNoVote::No)
.await
.unwrap();
@ -788,7 +788,7 @@ async fn test_cast_vote_with_early_vote_tipped_to_defeated() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie3, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie3, YesNoVote::No)
.await
.unwrap();
@ -846,7 +846,7 @@ async fn test_cast_vote_with_threshold_below_50_and_vote_not_tipped() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -915,7 +915,7 @@ async fn test_cast_vote_with_disabled_tipping_yes_votes() {
// Act
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie1,
YesNoVote::Yes,
@ -936,7 +936,7 @@ async fn test_cast_vote_with_disabled_tipping_yes_votes() {
.await
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie1, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie1, YesNoVote::No)
.await
.unwrap();
@ -986,7 +986,7 @@ async fn test_cast_vote_with_disabled_tipping_no_votes() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie1, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie1, YesNoVote::No)
.await
.unwrap();
@ -1039,7 +1039,7 @@ async fn test_cast_vote_with_voting_time_expired_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.err()
.unwrap();
@ -1081,7 +1081,7 @@ async fn test_cast_vote_with_cast_twice_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -1089,7 +1089,7 @@ async fn test_cast_vote_with_cast_twice_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -1130,7 +1130,7 @@ async fn test_cast_vote_with_invalid_proposal_owner_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -1172,7 +1172,7 @@ async fn test_cast_tipping_vote_with_invalid_proposal_owner_error() {
.unwrap();
governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie2,
YesNoVote::Yes,
@ -1185,7 +1185,7 @@ async fn test_cast_tipping_vote_with_invalid_proposal_owner_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -1226,7 +1226,7 @@ async fn test_cast_council_vote() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -98,7 +98,7 @@ async fn test_execute_transfer_from_native_treasury() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -13,7 +13,9 @@ async fn test_create_token_owner_record() {
let realm_cookie = governance_test.with_realm().await;
// Act
let token_owner_record_cookie = governance_test.with_token_owner_record(&realm_cookie).await;
let token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
// Assert
let token_owner_record_account = governance_test

View File

@ -64,7 +64,7 @@ async fn test_execute_mint_transaction() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -163,7 +163,7 @@ async fn test_execute_transfer_transaction() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -261,7 +261,7 @@ async fn test_execute_upgrade_program_transaction() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -447,7 +447,7 @@ async fn test_execute_proposal_transaction_with_invalid_state_errors() {
// Arrange
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -553,7 +553,7 @@ async fn test_execute_proposal_transaction_for_other_proposal_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -643,7 +643,7 @@ async fn test_execute_mint_transaction_twice_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -48,7 +48,7 @@ async fn test_finalize_vote_to_succeeded() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -147,7 +147,7 @@ async fn test_finalize_vote_to_defeated() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
@ -215,7 +215,7 @@ async fn test_finalize_vote_with_invalid_mint_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
@ -275,7 +275,7 @@ async fn test_finalize_vote_with_invalid_governance_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
@ -353,7 +353,7 @@ async fn test_finalize_council_vote() {
// Cast vote with 47% weight, above 40% quorum but below 50%+1 to tip automatically
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -53,7 +53,7 @@ async fn test_execute_flag_transaction_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -148,7 +148,7 @@ async fn test_execute_proposal_transaction_after_flagged_with_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -244,7 +244,7 @@ async fn test_execute_second_transaction_after_first_transaction_flagged_with_er
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -332,7 +332,7 @@ async fn test_flag_transaction_error_with_proposal_transaction_already_executed_
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -406,7 +406,7 @@ async fn test_flag_transaction_error_with_owner_or_delegate_must_sign_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -36,7 +36,7 @@ async fn test_relinquish_voted_proposal() {
.unwrap();
let mut vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -103,7 +103,7 @@ async fn test_relinquish_active_yes_vote() {
.unwrap();
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -171,7 +171,7 @@ async fn test_relinquish_active_no_vote() {
.unwrap();
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
@ -214,7 +214,7 @@ async fn test_relinquish_vote_with_invalid_mint_error() {
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
@ -228,17 +228,17 @@ async fn test_relinquish_vote_with_invalid_mint_error() {
.await
.unwrap();
let mut proposal_cookie = governance_test
let proposal_cookie = governance_test
.with_signed_off_proposal(&token_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
proposal_cookie.account.governing_token_mint = Pubkey::new_unique();
token_owner_record_cookie.account.governing_token_mint = Pubkey::new_unique();
// Act
@ -250,7 +250,7 @@ async fn test_relinquish_vote_with_invalid_mint_error() {
// Assert
assert_eq!(err, GovernanceError::InvalidGoverningMintForProposal.into());
assert_eq!(err, GovernanceError::InvalidGoverningTokenMint.into());
}
#[tokio::test]
@ -286,7 +286,7 @@ async fn test_relinquish_vote_with_governance_authority_must_sign_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
@ -352,12 +352,12 @@ async fn test_relinquish_vote_with_invalid_vote_record_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
let vote_record_cookie2 = governance_test
.with_cast_vote(
.with_cast_yes_no_vote(
&proposal_cookie,
&token_owner_record_cookie2,
YesNoVote::Yes,
@ -369,7 +369,7 @@ async fn test_relinquish_vote_with_invalid_vote_record_error() {
let err = governance_test
.relinquish_vote_using_instruction(&proposal_cookie, &token_owner_record_cookie, |i| {
i.accounts[3] = AccountMeta::new(vote_record_cookie2.address, false)
i.accounts[4] = AccountMeta::new(vote_record_cookie2.address, false)
// Try to use a vote_record for other token owner
})
.await
@ -412,7 +412,7 @@ async fn test_relinquish_vote_with_already_relinquished_error() {
.unwrap();
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
@ -482,7 +482,7 @@ async fn test_relinquish_proposal_in_voting_state_after_vote_time_ended() {
let clock = governance_test.bench.get_clock().await;
let mut vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -64,7 +64,7 @@ async fn test_set_governance_config() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -217,7 +217,7 @@ async fn test_set_governance_config_with_invalid_governance_authority_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -207,7 +207,7 @@ async fn test_withdraw_governing_tokens_with_unrelinquished_votes_error() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -253,7 +253,7 @@ async fn test_withdraw_governing_tokens_after_relinquishing_vote() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();

View File

@ -1,5 +1,5 @@
use solana_program::pubkey::Pubkey;
use spl_governance::state::realm::RealmConfigArgs;
use spl_governance::state::{enums::MintMaxVoteWeightSource, realm::RealmConfigArgs};
#[derive(Clone, Debug, PartialEq)]
pub struct SetRealmConfigArgs {
@ -7,3 +7,22 @@ pub struct SetRealmConfigArgs {
pub community_voter_weight_addin: Option<Pubkey>,
pub max_community_voter_weight_addin: Option<Pubkey>,
}
impl Default for SetRealmConfigArgs {
fn default() -> Self {
let realm_config_args = RealmConfigArgs {
use_council_mint: true,
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_weight_to_create_governance: 10,
use_community_voter_weight_addin: false,
use_max_community_voter_weight_addin: false,
};
Self {
realm_config_args,
community_voter_weight_addin: None,
max_community_voter_weight_addin: None,
}
}
}

View File

@ -448,12 +448,13 @@ impl GovernanceProgramTest {
&realm_cookie.account.community_mint,
&realm_cookie.community_mint_authority,
100,
None,
)
.await
}
#[allow(dead_code)]
pub async fn with_token_owner_record(
pub async fn with_community_token_owner_record(
&mut self,
realm_cookie: &RealmCookie,
) -> TokenOwnerRecordCookie {
@ -576,6 +577,7 @@ impl GovernanceProgramTest {
&realm_cookie.account.community_mint,
&realm_cookie.community_mint_authority,
amount,
None,
)
.await
}
@ -625,6 +627,7 @@ impl GovernanceProgramTest {
&realm_cookie.account.config.council_mint.unwrap(),
&realm_cookie.council_mint_authority.as_ref().unwrap(),
amount,
None,
)
.await
}
@ -639,6 +642,24 @@ impl GovernanceProgramTest {
&realm_cookie.account.config.council_mint.unwrap(),
realm_cookie.council_mint_authority.as_ref().unwrap(),
100,
None,
)
.await
}
#[allow(dead_code)]
pub async fn with_community_token_deposit_by_owner(
&mut self,
realm_cookie: &RealmCookie,
amount: u64,
token_owner: Keypair,
) -> Result<TokenOwnerRecordCookie, ProgramError> {
self.with_initial_governing_token_deposit(
&realm_cookie.address,
&realm_cookie.account.community_mint,
&realm_cookie.community_mint_authority,
amount,
Some(token_owner),
)
.await
}
@ -650,8 +671,9 @@ impl GovernanceProgramTest {
governing_mint: &Pubkey,
governing_mint_authority: &Keypair,
amount: u64,
token_owner: Option<Keypair>,
) -> Result<TokenOwnerRecordCookie, ProgramError> {
let token_owner = Keypair::new();
let token_owner = token_owner.unwrap_or(Keypair::new());
let token_source = Keypair::new();
let transfer_authority = Keypair::new();
@ -1194,7 +1216,7 @@ impl GovernanceProgramTest {
community_vote_threshold: VoteThreshold::YesVotePercentage(60),
vote_tipping: spl_governance::state::enums::VoteTipping::Strict,
council_vote_threshold: VoteThreshold::YesVotePercentage(80),
reserved: [0; 2],
council_veto_vote_threshold: VoteThreshold::YesVotePercentage(55),
}
}
@ -1833,7 +1855,7 @@ impl GovernanceProgramTest {
options: proposal_options,
deny_vote_weight,
veto_vote_weight: None,
veto_vote_weight: 0,
abstain_vote_weight: None,
execution_flags: InstructionExecutionFlags::None,
@ -1842,6 +1864,7 @@ impl GovernanceProgramTest {
vote_threshold: None,
reserved: [0; 64],
reserved1: 0,
};
let proposal_address = get_proposal_address(
@ -2068,10 +2091,11 @@ impl GovernanceProgramTest {
) -> Result<(), ProgramError> {
let mut relinquish_vote_ix = relinquish_vote(
&self.program_id,
&token_owner_record_cookie.account.realm,
&proposal_cookie.account.governance,
&proposal_cookie.address,
&token_owner_record_cookie.address,
&proposal_cookie.account.governing_token_mint,
&token_owner_record_cookie.account.governing_token_mint,
Some(token_owner_record_cookie.token_owner.pubkey()),
Some(self.bench.payer.pubkey()),
);
@ -2114,7 +2138,7 @@ impl GovernanceProgramTest {
}
#[allow(dead_code)]
pub async fn with_cast_vote(
pub async fn with_cast_yes_no_vote(
&mut self,
proposal_cookie: &ProposalCookie,
token_owner_record_cookie: &TokenOwnerRecordCookie,
@ -2128,12 +2152,12 @@ impl GovernanceProgramTest {
YesNoVote::No => Vote::Deny,
};
self.with_cast_multi_option_vote(proposal_cookie, token_owner_record_cookie, vote)
self.with_cast_vote(proposal_cookie, token_owner_record_cookie, vote)
.await
}
#[allow(dead_code)]
pub async fn with_cast_multi_option_vote(
pub async fn with_cast_vote(
&mut self,
proposal_cookie: &ProposalCookie,
token_owner_record_cookie: &TokenOwnerRecordCookie,
@ -2162,7 +2186,7 @@ impl GovernanceProgramTest {
&proposal_cookie.account.token_owner_record,
&token_owner_record_cookie.address,
&token_owner_record_cookie.token_owner.pubkey(),
&proposal_cookie.account.governing_token_mint,
&token_owner_record_cookie.account.governing_token_mint,
&self.bench.payer.pubkey(),
voter_weight_record,
max_voter_weight_record,

View File

@ -291,7 +291,7 @@ async fn test_vote_on_none_executable_single_choice_proposal_with_multiple_optio
// Act
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie, vote)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, vote)
.await
.unwrap();
@ -396,7 +396,7 @@ async fn test_vote_on_none_executable_multi_choice_proposal_with_multiple_option
// Act
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie, vote)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, vote)
.await
.unwrap();
@ -531,7 +531,7 @@ async fn test_vote_on_executable_proposal_with_multiple_options_and_partial_succ
]);
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie1, vote1)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie1, vote1)
.await
.unwrap();
@ -551,12 +551,12 @@ async fn test_vote_on_executable_proposal_with_multiple_options_and_partial_succ
]);
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie2, vote2)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, vote2)
.await
.unwrap();
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie3, Vote::Deny)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie3, Vote::Deny)
.await
.unwrap();
@ -706,7 +706,7 @@ async fn test_execute_proposal_with_multiple_options_and_partial_success() {
// yes threshold: 100
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie3, Vote::Deny)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie3, Vote::Deny)
.await
.unwrap();
@ -726,7 +726,7 @@ async fn test_execute_proposal_with_multiple_options_and_partial_success() {
]);
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie1, vote1)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie1, vote1)
.await
.unwrap();
@ -746,7 +746,7 @@ async fn test_execute_proposal_with_multiple_options_and_partial_success() {
]);
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie2, vote2)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, vote2)
.await
.unwrap();
@ -904,12 +904,12 @@ async fn test_try_execute_proposal_with_multiple_options_and_full_deny() {
.unwrap();
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie1, Vote::Deny)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie1, Vote::Deny)
.await
.unwrap();
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie2, Vote::Deny)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, Vote::Deny)
.await
.unwrap();
@ -1039,7 +1039,7 @@ async fn test_create_proposal_with_10_options_and_cast_vote() {
// Act
governance_test
.with_cast_multi_option_vote(&proposal_cookie, &token_owner_record_cookie, vote)
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, vote)
.await
.unwrap();

View File

@ -14,8 +14,9 @@ async fn test_cast_vote_with_all_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
// voter weight 120
governance_test
@ -48,7 +49,7 @@ async fn test_cast_vote_with_all_addin() {
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -73,8 +74,9 @@ async fn test_tip_vote_with_all_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
// voter weight 120
governance_test
@ -107,7 +109,7 @@ async fn test_tip_vote_with_all_addin() {
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();
@ -132,8 +134,9 @@ async fn test_finalize_vote_with_all_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
// voter weight 120
governance_test
@ -164,7 +167,7 @@ async fn test_finalize_vote_with_all_addin() {
.unwrap();
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::No)
.await
.unwrap();

View File

@ -43,7 +43,7 @@ async fn test_cast_vote_with_max_voter_weight_addin() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -92,7 +92,7 @@ async fn test_tip_vote_with_max_voter_weight_addin() {
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -142,7 +142,7 @@ async fn test_tip_vote_with_max_voter_weight_addin_and_max_below_total_cast_vote
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -191,7 +191,7 @@ async fn test_finalize_vote_with_max_voter_weight_addin() {
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -262,7 +262,7 @@ async fn test_finalize_vote_with_max_voter_weight_addin_and_max_below_total_cast
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -336,7 +336,7 @@ async fn test_cast_vote_with_max_voter_weight_addin_and_expired_record_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();

View File

@ -20,8 +20,9 @@ async fn test_create_governance_with_voter_weight_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record(&mut token_owner_record_cookie)
@ -54,8 +55,9 @@ async fn test_create_proposal_with_voter_weight_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record(&mut token_owner_record_cookie)
@ -93,8 +95,9 @@ async fn test_cast_vote_with_voter_weight_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record(&mut token_owner_record_cookie)
@ -117,7 +120,7 @@ async fn test_cast_vote_with_voter_weight_addin() {
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.unwrap();
@ -151,8 +154,9 @@ async fn test_create_token_governance_with_voter_weight_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record(&mut token_owner_record_cookie)
@ -185,8 +189,9 @@ async fn test_create_mint_governance_with_voter_weight_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record(&mut token_owner_record_cookie)
@ -219,8 +224,9 @@ async fn test_create_program_governance_with_voter_weight_addin() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record(&mut token_owner_record_cookie)
@ -277,8 +283,9 @@ async fn test_create_governance_with_voter_weight_action_error() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record_impl(
@ -314,8 +321,9 @@ async fn test_create_governance_with_voter_weight_expiry_error() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record_impl(
@ -353,8 +361,9 @@ async fn test_cast_vote_with_voter_weight_action_error() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record_impl(&mut token_owner_record_cookie, 100, None, None, None)
@ -391,7 +400,7 @@ async fn test_cast_vote_with_voter_weight_action_error() {
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.with_cast_yes_no_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
.await
.err()
.unwrap();
@ -408,8 +417,9 @@ async fn test_create_governance_with_voter_weight_action_target_error() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record_impl(
@ -450,8 +460,9 @@ async fn test_create_proposal_with_voter_weight_action_error() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test
.with_voter_weight_addin_record_impl(
@ -493,8 +504,9 @@ async fn test_create_governance_with_voter_weight_record() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
let mut token_owner_record_cookie = governance_test
.with_community_token_owner_record(&realm_cookie)
.await;
governance_test.advance_clock().await;
let clock = governance_test.bench.get_clock().await;

View File

@ -0,0 +1,719 @@
#![cfg(feature = "test-bpf")]
mod program_test;
use solana_program::instruction::AccountMeta;
use solana_program_test::tokio;
use program_test::*;
use spl_governance::{
error::GovernanceError,
state::{
enums::{ProposalState, VoteThreshold},
vote_record::Vote,
},
};
use spl_governance_test_sdk::tools::clone_keypair;
use self::args::SetRealmConfigArgs;
#[tokio::test]
async fn test_cast_veto_vote() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
// Mint extra council tokens for total supply of 120
governance_test.mint_council_tokens(&realm_cookie, 20).await;
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
let clock = governance_test.bench.get_clock().await;
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.unwrap();
// Assert
let vote_record_account = governance_test
.get_vote_record_account(&vote_record_cookie.address)
.await;
assert_eq!(vote_record_cookie.account, vote_record_account);
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(
token_owner_record_cookie
.account
.governing_token_deposit_amount,
proposal_account.veto_vote_weight
);
assert_eq!(proposal_account.state, ProposalState::Vetoed);
assert_eq!(
proposal_account.voting_completed_at,
Some(clock.unix_timestamp)
);
assert_eq!(Some(120), proposal_account.max_vote_weight);
assert_eq!(
Some(governance_cookie.account.config.council_veto_vote_threshold),
proposal_account.vote_threshold
);
let token_owner_record = governance_test
.get_token_owner_record_account(&token_owner_record_cookie.address)
.await;
assert_eq!(1, token_owner_record.unrelinquished_votes_count);
assert_eq!(1, token_owner_record.total_votes_count);
let realm_account = governance_test
.get_realm_account(&realm_cookie.address)
.await;
assert_eq!(0, realm_account.voting_proposal_count);
let governance_account = governance_test
.get_governance_account(&governance_cookie.address)
.await;
assert_eq!(0, governance_account.voting_proposal_count);
}
#[tokio::test]
async fn test_cast_veto_vote_with_community_not_allowed_to_vote_error() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.err()
.unwrap();
// Assert
assert_eq!(
err,
GovernanceError::GoverningTokenMintNotAllowedToVote.into()
);
}
#[tokio::test]
async fn test_cast_veto_vote_with_invalid_voting_mint_error() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Act
// Try to use Council Veto on Council vote Proposal
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.err()
.unwrap();
// Assert
assert_eq!(err, GovernanceError::InvalidGoverningMintForProposal.into());
}
#[tokio::test]
async fn test_cast_veto_vote_with_council_veto_vote_disabled_error() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
let mut governance_config = governance_test.get_default_governance_config();
governance_config.council_veto_vote_threshold = VoteThreshold::Disabled;
let mut governance_cookie = governance_test
.with_governance_using_config(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
&governance_config,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.err()
.unwrap();
// Assert
assert_eq!(
err,
GovernanceError::GoverningTokenMintNotAllowedToVote.into()
);
}
#[tokio::test]
async fn test_cast_veto_vote_without_tipping() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
// Mint extra council tokens for total supply of 201 to prevent tipping
governance_test
.mint_council_tokens(&realm_cookie, 101)
.await;
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.unwrap();
// Assert
let vote_record_account = governance_test
.get_vote_record_account(&vote_record_cookie.address)
.await;
assert_eq!(vote_record_cookie.account, vote_record_account);
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(
token_owner_record_cookie
.account
.governing_token_deposit_amount,
proposal_account.veto_vote_weight
);
assert_eq!(proposal_account.state, ProposalState::Voting);
}
#[tokio::test]
async fn test_cast_multiple_veto_votes_for_partially_approved_proposal() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
let token_owner_record_cookie2 = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
// Mint extra council tokens for total supply of 210 to prevent single vote tipping
governance_test.mint_council_tokens(&realm_cookie, 10).await;
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
// Mint extra council tokens for total supply of 200 to prevent single vote tipping
governance_test
.mint_community_tokens(&realm_cookie, 100)
.await;
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Partially approve Proposal
governance_test
.with_cast_yes_no_vote(
&proposal_cookie,
&proposal_owner_record_cookie,
YesNoVote::Yes,
)
.await
.unwrap();
// Partially Veto Proposal
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.unwrap();
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie2, Vote::Veto)
.await
.unwrap();
// Assert
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(200, proposal_account.veto_vote_weight);
assert_eq!(proposal_account.state, ProposalState::Vetoed);
}
#[tokio::test]
async fn test_cast_veto_vote_with_no_council_error() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let mut realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
let mut governance_config = governance_test.get_default_governance_config();
governance_config.council_veto_vote_threshold = VoteThreshold::Disabled;
let mut governance_cookie = governance_test
.with_governance_using_config(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
&governance_config,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Remove Council
let mut set_realm_config_args = SetRealmConfigArgs::default();
set_realm_config_args.realm_config_args.use_council_mint = false;
governance_test
.set_realm_config(&mut realm_cookie, &set_realm_config_args)
.await
.unwrap();
// Act
let err = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.err()
.unwrap();
// Assert
assert_eq!(err, GovernanceError::InvalidGoverningTokenMint.into());
}
#[tokio::test]
async fn test_relinquish_veto_vote() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
// Mint extra council tokens for total supply of 201 to prevent tipping
governance_test
.mint_council_tokens(&realm_cookie, 101)
.await;
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.unwrap();
// Act
governance_test
.relinquish_vote(&proposal_cookie, &token_owner_record_cookie)
.await
.unwrap();
// Assert
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(0, proposal_account.veto_vote_weight);
assert_eq!(proposal_account.state, ProposalState::Voting);
}
#[tokio::test]
async fn test_relinquish_veto_vote_with_vote_record_for_different_voting_mint_error() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let council_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
// Mint extra council tokens for total supply of 210
governance_test
.mint_council_tokens(&realm_cookie, 110)
.await;
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&council_token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
governance_test
.with_cast_vote(
&proposal_cookie,
&council_token_owner_record_cookie,
Vote::Veto,
)
.await
.unwrap();
// Create Community TokenOwnerRecord for council_token_owner and Cast Community vote
let community_token_owner_record_cookie = governance_test
.with_community_token_deposit_by_owner(
&realm_cookie,
100,
clone_keypair(&council_token_owner_record_cookie.token_owner),
)
.await
.unwrap();
// Mint extra council tokens for total supply of 250
governance_test
.mint_community_tokens(&realm_cookie, 150)
.await;
let community_vote_record_cookie = governance_test
.with_cast_yes_no_vote(
&proposal_cookie,
&community_token_owner_record_cookie,
YesNoVote::Yes,
)
.await
.unwrap();
// Act
let err = governance_test
.relinquish_vote_using_instruction(
&proposal_cookie,
&council_token_owner_record_cookie,
|i| {
// Try to use a vote_record from community Yes vote to relinquish council Veto vote
i.accounts[4] = AccountMeta::new(community_vote_record_cookie.address, false)
},
)
.await
.err()
.unwrap();
// Assert
assert_eq!(err, GovernanceError::InvalidGoverningMintForProposal.into());
}
#[tokio::test]
async fn test_cast_veto_vote_with_council_only_allowed_to_veto() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
// Allow Council to cast only Veto votes
let mut governance_config = governance_test.get_default_governance_config();
governance_config.council_vote_threshold = VoteThreshold::Disabled;
let mut governance_cookie = governance_test
.with_governance_using_config(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
&governance_config,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Act
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.unwrap();
// Assert
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(proposal_account.state, ProposalState::Vetoed);
}
#[tokio::test]
async fn test_cast_yes_and_veto_votes_with_yes_as_winning_vote() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await
.unwrap();
// Mint extra council tokens for total supply of 210 to prevent single vote tipping
governance_test
.mint_council_tokens(&realm_cookie, 110)
.await;
let mut governance_cookie = governance_test
.with_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&proposal_owner_record_cookie, &mut governance_cookie)
.await
.unwrap();
// Partially Veto Proposal
governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Veto)
.await
.unwrap();
// Act
// Approve Proposal
governance_test
.with_cast_yes_no_vote(
&proposal_cookie,
&proposal_owner_record_cookie,
YesNoVote::Yes,
)
.await
.unwrap();
// Assert
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(100, proposal_account.veto_vote_weight);
assert_eq!(proposal_account.state, ProposalState::Succeeded);
}
// TODO: Once Veto for Community or plugin support for Council is implemented write Veto tests with plugin
// The tests should cover scenarios where Veto voter_weight and/or max_voter_weight is resolved using the plugins