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:
parent
49c53ad653
commit
3d3b32dcf6
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue