Governance: Voters weights add-in (#2450)

* feat: setup and configure voter weight addin

feat: add use_voter_weight_add_in flag to realm config

chore: use spl-governance 1.1.1 version

chore: make clippy happy

chore: add test to deserialise v1 CreateRealm instruction from v2

feat: add voter-weight-addin skeleton project

chore: build voter-weight-addin before governance

fix: temp workaround to make spl_governance_voter_weight_addin available in CI

chore: add tests with voter-weight-addin

feat: implement deposit instruction for voter weight addin

feat: add voter_weight_expiry

fix: set voter_weight_expiry

chore: restore positive execute tests

chore: restore ignored tests

wip: pass voter weight accounts to create_account_governance2

wip: read voter weight account

chore: make clippy happy

wip: add realm and validation to voter_weight deposit

fix: update addin

chore: make clippy happy

chore: fix voter_weight_record names

feat: use voter weight provided by addin when governance created

chore: update addin

chore: remove governance stake pool program

feat: remove time offset from revise

chore: fix build

feat: create RealmAddins account when realm with addin is created

chore: make clippy happy

feat: set voter weight addin using SetRealmConfig instruction

chore: make clippy happy

chore: update comments

chore: reorder SetrealmConfig accounts

chore: infer use_community_voter_weight_addin

chore: infer use_community_voter_weight_addin

chore: update voter weight addin comments

feat: use voter weight addin id from RealmAddins account

* feat: use voter weight addin to create proposal

* feat: use voter weight addin to cast vote

* chore: make clippy happy

* feat: use voter weight addin to create token governance

* feat: use voter weight addin to create mint governance

* feat: use voter weight adding to create program governance

* chore: create assert_can_withdraw_governing_tokens() helper function

* chore: fix compilation

* fix: ensure governance authority signed transaction to create governance

* feat: implement CreateTokenOwnerRecord instruction

* chore: fix chat tests

* chore: update comments

* chore: rename RealmAddins account to RealmConfig account

* chore: add more reserved space to GovernanceConfig account

* chore: update instruction comments

* chore: update comments

* chore: fix compilation

* chore: remove ignore directive for tests

* feat: panic when depositing tokens into a realm with voter weight addin

* chore: rename community_voter_weight to community_voter_weight_addin

* feat: make payer account optional for SetRealmConfig
This commit is contained in:
Sebastian Bor 2021-10-13 13:15:22 +01:00 committed by GitHub
parent 1c417ffa4f
commit c99f4195f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2262 additions and 240 deletions

45
Cargo.lock generated
View File

@ -3657,6 +3657,24 @@ dependencies = [
[[package]]
name = "spl-governance"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae3df1aa25c5f5ce7a6595959b1b02c6ae6ea35274379ee64bcf80bae11a767"
dependencies = [
"arrayref",
"bincode",
"borsh",
"num-derive",
"num-traits",
"serde",
"serde_derive",
"solana-program",
"spl-token 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"thiserror",
]
[[package]]
name = "spl-governance"
version = "2.1.1"
dependencies = [
"arrayref",
"assert_matches",
@ -3671,6 +3689,7 @@ dependencies = [
"solana-program",
"solana-program-test",
"solana-sdk",
"spl-governance 1.1.1",
"spl-governance-test-sdk",
"spl-token 3.2.0",
"thiserror",
@ -3693,7 +3712,7 @@ dependencies = [
"solana-program",
"solana-program-test",
"solana-sdk",
"spl-governance",
"spl-governance 2.1.1",
"spl-governance-test-sdk",
"spl-token 3.2.0",
"thiserror",
@ -3717,6 +3736,30 @@ dependencies = [
"thiserror",
]
[[package]]
name = "spl-governance-voter-weight-addin"
version = "0.1.0"
dependencies = [
"arrayref",
"assert_matches",
"base64 0.13.0",
"bincode",
"borsh",
"num-derive",
"num-traits",
"proptest",
"serde",
"serde_derive",
"solana-program",
"solana-program-test",
"solana-sdk",
"spl-governance 2.1.1",
"spl-governance-chat",
"spl-governance-test-sdk",
"spl-token 3.2.0",
"thiserror",
]
[[package]]
name = "spl-math"
version = "0.1.0"

View File

@ -10,6 +10,7 @@ members = [
"examples/rust/transfer-lamports",
"feature-proposal/program",
"feature-proposal/cli",
"governance/voter-weight-addin/program",
"governance/program",
"governance/test-sdk",
"governance/chat/program",

View File

@ -21,7 +21,7 @@ serde = "1.0.127"
serde_derive = "1.0.103"
solana-program = "1.8.0"
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] }
spl-governance= { version = "1.1.0", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance= { version = "2.1.0", path ="../../program", features = [ "no-entrypoint" ]}
thiserror = "1.0"

View File

@ -87,6 +87,7 @@ impl GovernanceChatProgramTest {
&governing_token_mint_keypair.pubkey(),
&self.bench.payer.pubkey(),
None,
None,
name.clone(),
1,
MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION,
@ -155,11 +156,13 @@ impl GovernanceChatProgramTest {
&governed_account_address,
&token_owner_record_address,
&self.bench.payer.pubkey(),
&token_owner.pubkey(),
None,
governance_config,
);
self.bench
.process_transaction(&[create_account_governance_ix], None)
.process_transaction(&[create_account_governance_ix], Some(&[&token_owner]))
.await
.unwrap();
@ -173,7 +176,7 @@ impl GovernanceChatProgramTest {
let proposal_name = "Proposal #1".to_string();
let description_link = "Proposal Description".to_string();
let proposal_index = 0;
let proposal_index: u32 = 0;
let create_proposal_ix = create_proposal(
&self.governance_program_id,
@ -181,6 +184,7 @@ impl GovernanceChatProgramTest {
&token_owner_record_address,
&token_owner.pubkey(),
&self.bench.payer.pubkey(),
None,
&realm_address,
proposal_name,
description_link.clone(),

View File

@ -1,6 +1,6 @@
[package]
name = "spl-governance"
version = "1.1.1"
version = "2.1.1"
description = "Solana Program Library Governance Program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library"
@ -30,6 +30,8 @@ proptest = "1.0"
solana-program-test = "1.8.0"
solana-sdk = "1.8.0"
spl-governance-test-sdk = { version = "0.1.0", path ="../test-sdk"}
spl-governance-v1 = {package="spl-governance", version = "1.1.1", features = [ "no-entrypoint" ] }
[lib]
crate-type = ["cdylib", "lib"]

View File

@ -0,0 +1,2 @@
//! Governance add-ins interfaces
pub mod voter_weight;

View File

@ -0,0 +1,110 @@
//! VoterWeight Addin interface
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{
account_info::AccountInfo,
clock::{Clock, Slot},
program_error::ProgramError,
program_pack::IsInitialized,
pubkey::Pubkey,
sysvar::Sysvar,
};
use crate::{
error::GovernanceError,
state::token_owner_record::TokenOwnerRecord,
tools::account::{get_account_data, AccountMaxSize},
};
/// VoterWeight account type
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum VoterWeightAccountType {
/// Default uninitialized account state
Uninitialized,
/// Voter Weight Record
VoterWeightRecord,
}
/// VoterWeight Record account
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct VoterWeightRecord {
/// VoterWeightRecord account type
pub account_type: VoterWeightAccountType,
/// The Realm the VoterWeightRecord belongs to
pub realm: Pubkey,
/// Governing Token Mint the VoterWeightRecord is associated with
/// Note: The addin can take deposits of any tokens and is not restricted to the community or council tokens only
// The mint here is to link the record to either community or council mint of the realm
pub governing_token_mint: Pubkey,
/// The owner of the governing token and voter
pub governing_token_owner: Pubkey,
/// Voter's weight
pub voter_weight: u64,
/// The slot when the voting weight expires
/// It should be set to None if the weight never expires
/// If the voter weight decays with time, for example for time locked based weights, then the expiry must be set
/// As a common pattern Revise instruction to update the weight should be invoked before governance instruction within the same transaction
/// and the expiry set to the current slot to provide up to date weight
pub voter_weight_expiry: Option<Slot>,
}
impl AccountMaxSize for VoterWeightRecord {}
impl IsInitialized for VoterWeightRecord {
fn is_initialized(&self) -> bool {
self.account_type == VoterWeightAccountType::VoterWeightRecord
}
}
impl VoterWeightRecord {
/// Asserts the VoterWeightRecord hasn't expired
pub fn assert_is_up_to_date(&self) -> Result<(), ProgramError> {
if let Some(voter_weight_expiry) = self.voter_weight_expiry {
let slot = Clock::get().unwrap().slot;
if slot > voter_weight_expiry {
return Err(GovernanceError::VoterWeightRecordExpired.into());
}
}
Ok(())
}
}
/// Deserializes VoterWeightRecord account and checks owner program
pub fn get_voter_weight_record_data(
program_id: &Pubkey,
voter_weight_record_info: &AccountInfo,
) -> Result<VoterWeightRecord, ProgramError> {
get_account_data::<VoterWeightRecord>(voter_weight_record_info, program_id)
}
/// Deserializes VoterWeightRecord account, checks owner program and asserts it's for the same realm, mint and token owner as the provided TokenOwnerRecord
pub fn get_voter_weight_record_data_for_token_owner_record(
program_id: &Pubkey,
voter_weight_record_info: &AccountInfo,
token_owner_record: &TokenOwnerRecord,
) -> Result<VoterWeightRecord, ProgramError> {
let voter_weight_record_data =
get_voter_weight_record_data(program_id, voter_weight_record_info)?;
if voter_weight_record_data.realm != token_owner_record.realm {
return Err(GovernanceError::InvalidVoterWeightRecordForRealm.into());
}
if voter_weight_record_data.governing_token_mint != token_owner_record.governing_token_mint {
return Err(GovernanceError::InvalidVoterWeightRecordForGoverningTokenMint.into());
}
if voter_weight_record_data.governing_token_owner != token_owner_record.governing_token_owner {
return Err(GovernanceError::InvalidVoterWeightRecordForTokenOwner.into());
}
Ok(voter_weight_record_data)
}

View File

@ -323,6 +323,34 @@ pub enum GovernanceError {
/// All proposals must be finalized to withdraw governing tokens
#[error("All proposals must be finalized to withdraw governing tokens")]
AllProposalsMustBeFinalisedToWithdrawGoverningTokens,
/// Invalid VoterWeightRecord for Realm
#[error("Invalid VoterWeightRecord for Realm")]
InvalidVoterWeightRecordForRealm,
/// Invalid VoterWeightRecord for GoverningTokenMint
#[error("Invalid VoterWeightRecord for GoverningTokenMint")]
InvalidVoterWeightRecordForGoverningTokenMint,
/// Invalid VoterWeightRecord for TokenOwner
#[error("Invalid VoterWeightRecord for TokenOwner")]
InvalidVoterWeightRecordForTokenOwner,
/// VoterWeightRecord expired
#[error("VoterWeightRecord expired")]
VoterWeightRecordExpired,
/// Invalid RealmConfig for Realm
#[error("Invalid RealmConfig for Realm")]
InvalidRealmConfigForRealm,
/// TokenOwnerRecord already exists
#[error("TokenOwnerRecord already exists")]
TokenOwnerRecordAlreadyExists,
/// Governing token deposits not allowed
#[error("Governing token deposits not allowed")]
GoverningTokenDepositsNotAllowed,
}
impl PrintProgramError for GovernanceError {

View File

@ -10,6 +10,7 @@ use crate::{
proposal::get_proposal_address,
proposal_instruction::{get_proposal_instruction_address, InstructionData},
realm::{get_governing_token_holding_address, get_realm_address, RealmConfigArgs},
realm_config::get_realm_config_address,
signatory_record::get_signatory_record_address,
token_owner_record::get_token_owner_record_address,
vote_record::get_vote_record_address,
@ -50,9 +51,13 @@ pub enum GovernanceInstruction {
/// 5. `[]` System
/// 6. `[]` SPL Token
/// 7. `[]` Sysvar Rent
/// 8. `[]` Council Token Mint - optional
/// 9. `[writable]` Council Token Holding account - optional unless council is used. PDA seeds: ['governance',realm,council_mint]
/// The account will be created with the Realm PDA as its owner
/// 10. `[writable]` RealmConfig account. PDA seeds: ['realm-config', realm]
/// 11. `[]` Optional Community Voter Weight Addin Program Id
CreateRealm {
#[allow(dead_code)]
/// UTF-8 encoded Governance Realm name
@ -113,6 +118,9 @@ pub enum GovernanceInstruction {
/// 4. `[signer]` Payer
/// 5. `[]` System program
/// 6. `[]` Sysvar Rent
/// 7. `[signer]` Governance authority
/// 8. `[]` Optional Realm Config
/// 9. `[]` Optional Voter Weight Record
CreateAccountGovernance {
/// Governance config
#[allow(dead_code)]
@ -131,6 +139,9 @@ pub enum GovernanceInstruction {
/// 7. `[]` bpf_upgradeable_loader program
/// 8. `[]` System program
/// 9. `[]` Sysvar Rent
/// 10. `[signer]` Governance authority
/// 11. `[]` Optional Realm Config
/// 12. `[]` Optional Voter Weight Record
CreateProgramGovernance {
/// Governance config
#[allow(dead_code)]
@ -154,6 +165,8 @@ pub enum GovernanceInstruction {
/// 6. `[]` System program
/// 7. `[]` Rent sysvar
/// 8. `[]` Clock sysvar
/// 9. `[]` Optional Realm Config
/// 10. `[]` Optional Voter Weight Record
CreateProposal {
#[allow(dead_code)]
/// UTF-8 encoded name of the proposal
@ -263,6 +276,8 @@ pub enum GovernanceInstruction {
/// 8. `[]` System program
/// 9. `[]` Rent sysvar
/// 10. `[]` Clock sysvar
/// 11. `[]` Optional Realm Config
/// 12. `[]` Optional Voter Weight Record
CastVote {
#[allow(dead_code)]
/// Yes/No vote
@ -317,6 +332,9 @@ pub enum GovernanceInstruction {
/// 6. `[]` SPL Token program
/// 7. `[]` System program
/// 8. `[]` Sysvar Rent
/// 8. `[signer]` Governance authority
/// 9. `[]` Optional Realm Config
/// 10. `[]` Optional Voter Weight Record
CreateMintGovernance {
#[allow(dead_code)]
/// Governance config
@ -340,6 +358,9 @@ pub enum GovernanceInstruction {
/// 6. `[]` SPL Token program
/// 7. `[]` System program
/// 8. `[]` Sysvar Rent
/// 9. `[signer]` Governance authority
/// 10. `[]` Optional Realm Config
/// 11. `[]` Optional Voter Weight Record
CreateTokenGovernance {
#[allow(dead_code)]
/// Governance config
@ -393,11 +414,26 @@ pub enum GovernanceInstruction {
/// If that's required then it must be done before executing this instruction
/// 3. `[writable]` Council Token Holding account - optional unless council is used. PDA seeds: ['governance',realm,council_mint]
/// The account will be created with the Realm PDA as its owner
/// 4. `[]` System
/// 5. `[writable]` RealmConfig account. PDA seeds: ['realm-config', realm]
/// 6. `[signer]` Optional Payer
/// 7. `[]` Optional Community Voter Weight Addin Program Id
SetRealmConfig {
#[allow(dead_code)]
/// Realm config args
config_args: RealmConfigArgs,
},
/// Creates TokenOwnerRecord with 0 deposit amount
/// It's used to register TokenOwner when voter weight addin is used and the Governance program doesn't take deposits
///
/// 0. `[]` Realm account
/// 1. `[]` Governing Token Owner account
/// 2. `[writable]` TokenOwnerRecord account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
/// 3. `[]` Governing Token Mint
/// 4. `[signer]` Payer
/// 5. `[]` System
CreateTokenOwnerRecord {},
}
/// Creates CreateRealm instruction
@ -409,6 +445,7 @@ pub fn create_realm(
community_token_mint: &Pubkey,
payer: &Pubkey,
council_token_mint: Option<Pubkey>,
community_voter_weight_addin: Option<Pubkey>,
// Args
name: String,
min_community_tokens_to_create_governance: u64,
@ -440,11 +477,26 @@ pub fn create_realm(
false
};
let realm_config_address = get_realm_config_address(program_id, &realm_address);
accounts.push(AccountMeta::new(realm_config_address, false));
let use_community_voter_weight_addin =
if let Some(community_voter_weight_addin) = community_voter_weight_addin {
accounts.push(AccountMeta::new_readonly(
community_voter_weight_addin,
false,
));
true
} else {
false
};
let instruction = GovernanceInstruction::CreateRealm {
config_args: RealmConfigArgs {
use_council_mint,
min_community_tokens_to_create_governance,
community_mint_max_vote_weight_source,
use_community_voter_weight_addin,
},
name,
};
@ -572,7 +624,8 @@ pub fn set_governance_delegate(
}
}
/// Creates CreateAccountGovernance instruction
/// Creates CreateAccountGovernance instruction using optional voter weight addin
#[allow(clippy::too_many_arguments)]
pub fn create_account_governance(
program_id: &Pubkey,
// Accounts
@ -580,13 +633,15 @@ pub fn create_account_governance(
governed_account: &Pubkey,
token_owner_record: &Pubkey,
payer: &Pubkey,
governance_authority: &Pubkey,
voter_weight_record: Option<Pubkey>,
// Args
config: GovernanceConfig,
) -> Instruction {
let account_governance_address =
get_account_governance_address(program_id, realm, governed_account);
let accounts = vec![
let mut accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new(account_governance_address, false),
AccountMeta::new_readonly(*governed_account, false),
@ -594,8 +649,11 @@ pub fn create_account_governance(
AccountMeta::new_readonly(*payer, true),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(*governance_authority, true),
];
with_voter_weight_accounts(program_id, &mut accounts, realm, voter_weight_record);
let instruction = GovernanceInstruction::CreateAccountGovernance { config };
Instruction {
@ -615,6 +673,8 @@ pub fn create_program_governance(
governed_program_upgrade_authority: &Pubkey,
token_owner_record: &Pubkey,
payer: &Pubkey,
governance_authority: &Pubkey,
voter_weight_record: Option<Pubkey>,
// Args
config: GovernanceConfig,
transfer_upgrade_authority: bool,
@ -623,7 +683,7 @@ pub fn create_program_governance(
get_program_governance_address(program_id, realm, governed_program);
let governed_program_data_address = get_program_data_address(governed_program);
let accounts = vec![
let mut accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new(program_governance_address, false),
AccountMeta::new_readonly(*governed_program, false),
@ -634,8 +694,11 @@ pub fn create_program_governance(
AccountMeta::new_readonly(bpf_loader_upgradeable::id(), false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(*governance_authority, true),
];
with_voter_weight_accounts(program_id, &mut accounts, realm, voter_weight_record);
let instruction = GovernanceInstruction::CreateProgramGovernance {
config,
transfer_upgrade_authority,
@ -658,13 +721,15 @@ pub fn create_mint_governance(
governed_mint_authority: &Pubkey,
token_owner_record: &Pubkey,
payer: &Pubkey,
governance_authority: &Pubkey,
voter_weight_record: Option<Pubkey>,
// Args
config: GovernanceConfig,
transfer_mint_authority: bool,
) -> Instruction {
let mint_governance_address = get_mint_governance_address(program_id, realm, governed_mint);
let accounts = vec![
let mut accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new(mint_governance_address, false),
AccountMeta::new(*governed_mint, false),
@ -674,8 +739,11 @@ pub fn create_mint_governance(
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(*governance_authority, true),
];
with_voter_weight_accounts(program_id, &mut accounts, realm, voter_weight_record);
let instruction = GovernanceInstruction::CreateMintGovernance {
config,
transfer_mint_authority,
@ -698,13 +766,15 @@ pub fn create_token_governance(
governed_token_owner: &Pubkey,
token_owner_record: &Pubkey,
payer: &Pubkey,
governance_authority: &Pubkey,
voter_weight_record: Option<Pubkey>,
// Args
config: GovernanceConfig,
transfer_token_owner: bool,
) -> Instruction {
let token_governance_address = get_token_governance_address(program_id, realm, governed_token);
let accounts = vec![
let mut accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new(token_governance_address, false),
AccountMeta::new(*governed_token, false),
@ -714,8 +784,11 @@ pub fn create_token_governance(
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(*governance_authority, true),
];
with_voter_weight_accounts(program_id, &mut accounts, realm, voter_weight_record);
let instruction = GovernanceInstruction::CreateTokenGovernance {
config,
transfer_token_owner,
@ -737,6 +810,7 @@ pub fn create_proposal(
proposal_owner_record: &Pubkey,
governance_authority: &Pubkey,
payer: &Pubkey,
voter_weight_record: Option<Pubkey>,
// Args
realm: &Pubkey,
name: String,
@ -751,7 +825,7 @@ pub fn create_proposal(
&proposal_index.to_le_bytes(),
);
let accounts = vec![
let mut accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new(proposal_address, false),
AccountMeta::new(*governance, false),
@ -763,6 +837,8 @@ pub fn create_proposal(
AccountMeta::new_readonly(sysvar::clock::id(), false),
];
with_voter_weight_accounts(program_id, &mut accounts, realm, voter_weight_record);
let instruction = GovernanceInstruction::CreateProposal {
name,
description_link,
@ -879,13 +955,14 @@ pub fn cast_vote(
governance_authority: &Pubkey,
governing_token_mint: &Pubkey,
payer: &Pubkey,
voter_weight_record: Option<Pubkey>,
// Args
vote: Vote,
) -> Instruction {
let vote_record_address =
get_vote_record_address(program_id, proposal, voter_token_owner_record);
let accounts = vec![
let mut accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(*governance, false),
AccountMeta::new(*proposal, false),
@ -900,6 +977,8 @@ pub fn cast_vote(
AccountMeta::new_readonly(sysvar::clock::id(), false),
];
with_voter_weight_accounts(program_id, &mut accounts, realm, voter_weight_record);
let instruction = GovernanceInstruction::CastVote { vote };
Instruction {
@ -1165,13 +1244,15 @@ pub fn set_realm_authority(
}
/// Creates SetRealmConfig instruction
#[allow(clippy::too_many_arguments)]
pub fn set_realm_config(
program_id: &Pubkey,
// Accounts
realm: &Pubkey,
realm_authority: &Pubkey,
council_token_mint: Option<Pubkey>,
payer: &Pubkey,
community_voter_weight_addin: Option<Pubkey>,
// Args
min_community_tokens_to_create_governance: u64,
community_mint_max_vote_weight_source: MintMaxVoteWeightSource,
@ -1192,11 +1273,31 @@ pub fn set_realm_config(
false
};
accounts.push(AccountMeta::new_readonly(system_program::id(), false));
// Always pass realm_config_address because it's needed when use_community_voter_weight_addin is set to true
// but also when it's set to false and the addin is being removed from the realm
let realm_config_address = get_realm_config_address(program_id, realm);
accounts.push(AccountMeta::new(realm_config_address, false));
let use_community_voter_weight_addin =
if let Some(community_voter_weight_addin) = community_voter_weight_addin {
accounts.push(AccountMeta::new(*payer, true));
accounts.push(AccountMeta::new_readonly(
community_voter_weight_addin,
false,
));
true
} else {
false
};
let instruction = GovernanceInstruction::SetRealmConfig {
config_args: RealmConfigArgs {
use_council_mint,
min_community_tokens_to_create_governance,
community_mint_max_vote_weight_source,
use_community_voter_weight_addin,
},
};
@ -1206,3 +1307,51 @@ pub fn set_realm_config(
data: instruction.try_to_vec().unwrap(),
}
}
/// Adds voter weight accounts to the given accounts if voter_weight_record is Some
pub fn with_voter_weight_accounts(
program_id: &Pubkey,
accounts: &mut Vec<AccountMeta>,
realm: &Pubkey,
voter_weight_record: Option<Pubkey>,
) {
if let Some(voter_weight_record) = voter_weight_record {
let realm_config_address = get_realm_config_address(program_id, realm);
accounts.push(AccountMeta::new_readonly(realm_config_address, false));
accounts.push(AccountMeta::new_readonly(voter_weight_record, false));
}
}
/// Creates CreateTokenOwnerRecord instruction
pub fn create_token_owner_record(
program_id: &Pubkey,
// Accounts
realm: &Pubkey,
governing_token_owner: &Pubkey,
governing_token_mint: &Pubkey,
payer: &Pubkey,
) -> Instruction {
let token_owner_record_address = get_token_owner_record_address(
program_id,
realm,
governing_token_mint,
governing_token_owner,
);
let accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(*governing_token_owner, false),
AccountMeta::new(token_owner_record_address, false),
AccountMeta::new_readonly(*governing_token_mint, false),
AccountMeta::new_readonly(*payer, true),
AccountMeta::new_readonly(system_program::id(), false),
];
let instruction = GovernanceInstruction::CreateTokenOwnerRecord {};
Instruction {
program_id: *program_id,
accounts,
data: instruction.try_to_vec().unwrap(),
}
}

View File

@ -1,6 +1,7 @@
#![deny(missing_docs)]
//! A Governance program for the Solana blockchain.
pub mod addins;
pub mod entrypoint;
pub mod error;
pub mod instruction;

View File

@ -9,6 +9,7 @@ mod process_create_program_governance;
mod process_create_proposal;
mod process_create_realm;
mod process_create_token_governance;
mod process_create_token_owner_record;
mod process_deposit_governing_tokens;
mod process_execute_instruction;
mod process_finalize_vote;
@ -36,6 +37,7 @@ use process_create_program_governance::*;
use process_create_proposal::*;
use process_create_realm::*;
use process_create_token_governance::*;
use process_create_token_owner_record::*;
use process_deposit_governing_tokens::*;
use process_execute_instruction::*;
use process_finalize_vote::*;
@ -176,5 +178,8 @@ pub fn process_instruction(
GovernanceInstruction::SetRealmConfig { config_args } => {
process_set_realm_config(program_id, accounts, config_args)
}
GovernanceInstruction::CreateTokenOwnerRecord {} => {
process_create_token_owner_record(program_id, accounts)
}
}
}

View File

@ -98,23 +98,28 @@ pub fn process_cast_vote(
.checked_add(1)
.unwrap();
let vote_amount = voter_token_owner_record_data.governing_token_deposit_amount;
let voter_weight = voter_token_owner_record_data.resolve_voter_weight(
program_id,
account_info_iter,
realm_info.key,
&realm_data,
)?;
// Calculate Proposal voting weights
let vote_weight = match vote {
Vote::Yes => {
proposal_data.yes_votes_count = proposal_data
.yes_votes_count
.checked_add(vote_amount)
.checked_add(voter_weight)
.unwrap();
VoteWeight::Yes(vote_amount)
VoteWeight::Yes(voter_weight)
}
Vote::No => {
proposal_data.no_votes_count = proposal_data
.no_votes_count
.checked_add(vote_amount)
.checked_add(voter_weight)
.unwrap();
VoteWeight::No(vote_amount)
VoteWeight::No(voter_weight)
}
};

View File

@ -40,13 +40,24 @@ pub fn process_create_account_governance(
let rent_sysvar_info = next_account_info(account_info_iter)?; // 6
let rent = &Rent::from_account_info(rent_sysvar_info)?;
let governance_authority_info = next_account_info(account_info_iter)?; // 7
assert_valid_create_governance_args(program_id, &config, realm_info)?;
let realm_data = get_realm_data(program_id, realm_info)?;
let token_owner_record_data =
get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?;
token_owner_record_data.assert_can_create_governance(&realm_data)?;
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
let voter_weight = token_owner_record_data.resolve_voter_weight(
program_id,
account_info_iter,
realm_info.key,
&realm_data,
)?;
token_owner_record_data.assert_can_create_governance(&realm_data, voter_weight)?;
let account_governance_data = Governance {
account_type: GovernanceAccountType::AccountGovernance,

View File

@ -48,13 +48,24 @@ pub fn process_create_mint_governance(
let rent_sysvar_info = next_account_info(account_info_iter)?; // 8
let rent = &Rent::from_account_info(rent_sysvar_info)?;
let governance_authority_info = next_account_info(account_info_iter)?; // 9
assert_valid_create_governance_args(program_id, &config, realm_info)?;
let realm_data = get_realm_data(program_id, realm_info)?;
let token_owner_record_data =
get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?;
token_owner_record_data.assert_can_create_governance(&realm_data)?;
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
let voter_weight = token_owner_record_data.resolve_voter_weight(
program_id,
account_info_iter,
realm_info.key,
&realm_data,
)?;
token_owner_record_data.assert_can_create_governance(&realm_data, voter_weight)?;
let mint_governance_data = Governance {
account_type: GovernanceAccountType::MintGovernance,

View File

@ -52,13 +52,24 @@ pub fn process_create_program_governance(
let rent_sysvar_info = next_account_info(account_info_iter)?; // 9
let rent = &Rent::from_account_info(rent_sysvar_info)?;
let governance_authority_info = next_account_info(account_info_iter)?; // 10
assert_valid_create_governance_args(program_id, &config, realm_info)?;
let realm_data = get_realm_data(program_id, realm_info)?;
let token_owner_record_data =
get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?;
token_owner_record_data.assert_can_create_governance(&realm_data)?;
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
let voter_weight = token_owner_record_data.resolve_voter_weight(
program_id,
account_info_iter,
realm_info.key,
&realm_data,
)?;
token_owner_record_data.assert_can_create_governance(&realm_data, voter_weight)?;
let program_governance_data = Governance {
account_type: GovernanceAccountType::ProgramGovernance,

View File

@ -68,8 +68,19 @@ pub fn process_create_proposal(
proposal_owner_record_data
.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
let voter_weight = proposal_owner_record_data.resolve_voter_weight(
program_id,
account_info_iter,
realm_info.key,
&realm_data,
)?;
// Ensure proposal owner (TokenOwner) has enough tokens to create proposal and no outstanding proposals
proposal_owner_record_data.assert_can_create_proposal(&realm_data, &governance_data.config)?;
proposal_owner_record_data.assert_can_create_proposal(
&realm_data,
&governance_data.config,
voter_weight,
)?;
proposal_owner_record_data.outstanding_proposal_count = proposal_owner_record_data
.outstanding_proposal_count

View File

@ -16,6 +16,7 @@ use crate::{
assert_valid_realm_config_args, get_governing_token_holding_address_seeds,
get_realm_address_seeds, Realm, RealmConfig, RealmConfigArgs,
},
realm_config::{get_realm_config_address_seeds, RealmConfigAccount},
},
tools::{
account::create_and_serialize_account_signed, spl_token::create_spl_token_account_signed,
@ -62,8 +63,8 @@ pub fn process_create_realm(
)?;
let council_token_mint_address = if config_args.use_council_mint {
let council_token_mint_info = next_account_info(account_info_iter)?;
let council_token_holding_info = next_account_info(account_info_iter)?;
let council_token_mint_info = next_account_info(account_info_iter)?; // 8
let council_token_holding_info = next_account_info(account_info_iter)?; // 9
create_spl_token_account_signed(
payer_info,
@ -83,6 +84,31 @@ pub fn process_create_realm(
None
};
if config_args.use_community_voter_weight_addin {
let realm_config_info = next_account_info(account_info_iter)?; // 10
let community_voter_weight_addin_info = next_account_info(account_info_iter)?; //11
let realm_config_data = RealmConfigAccount {
account_type: GovernanceAccountType::RealmConfig,
realm: *realm_info.key,
community_voter_weight_addin: Some(*community_voter_weight_addin_info.key),
reserved_1: None,
reserved_2: None,
reserved_3: None,
reserved: [0; 128],
};
create_and_serialize_account_signed::<RealmConfigAccount>(
payer_info,
realm_config_info,
&realm_config_data,
&get_realm_config_address_seeds(realm_info.key),
program_id,
system_info,
rent,
)?;
}
let realm_data = Realm {
account_type: GovernanceAccountType::Realm,
community_mint: *governance_token_mint_info.key,
@ -92,11 +118,12 @@ pub fn process_create_realm(
authority: Some(*realm_authority_info.key),
config: RealmConfig {
council_mint: council_token_mint_address,
reserved: [0; 8],
reserved: [0; 7],
community_mint_max_vote_weight_source: config_args
.community_mint_max_vote_weight_source,
min_community_tokens_to_create_governance: config_args
.min_community_tokens_to_create_governance,
use_community_voter_weight_addin: config_args.use_community_voter_weight_addin,
},
};

View File

@ -48,13 +48,24 @@ pub fn process_create_token_governance(
let rent_sysvar_info = next_account_info(account_info_iter)?; // 8
let rent = &Rent::from_account_info(rent_sysvar_info)?;
let governance_authority_info = next_account_info(account_info_iter)?; // 9
assert_valid_create_governance_args(program_id, &config, realm_info)?;
let realm_data = get_realm_data(program_id, realm_info)?;
let token_owner_record_data =
get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm_info.key)?;
token_owner_record_data.assert_can_create_governance(&realm_data)?;
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
let voter_weight = token_owner_record_data.resolve_voter_weight(
program_id,
account_info_iter,
realm_info.key,
&realm_data,
)?;
token_owner_record_data.assert_can_create_governance(&realm_data, voter_weight)?;
let token_governance_data = Governance {
account_type: GovernanceAccountType::TokenGovernance,

View File

@ -0,0 +1,69 @@
//! Program state processor
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
};
use crate::{
error::GovernanceError,
state::{
enums::GovernanceAccountType,
realm::get_realm_data,
token_owner_record::{get_token_owner_record_address_seeds, TokenOwnerRecord},
},
tools::account::create_and_serialize_account_signed,
};
/// Processes CreateTokenOwnerRecord instruction
pub fn process_create_token_owner_record(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let realm_info = next_account_info(account_info_iter)?; // 0
let governing_token_owner_info = next_account_info(account_info_iter)?; // 1
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
let governing_token_mint_info = next_account_info(account_info_iter)?; // 3
let payer_info = next_account_info(account_info_iter)?; // 4
let system_info = next_account_info(account_info_iter)?; // 5
let rent = Rent::get().unwrap();
let realm_data = get_realm_data(program_id, realm_info)?;
realm_data.assert_is_valid_governing_token_mint(governing_token_mint_info.key)?;
if !token_owner_record_info.data_is_empty() {
return Err(GovernanceError::TokenOwnerRecordAlreadyExists.into());
}
let token_owner_record_data = TokenOwnerRecord {
account_type: GovernanceAccountType::TokenOwnerRecord,
realm: *realm_info.key,
governing_token_owner: *governing_token_owner_info.key,
governing_token_deposit_amount: 0,
governing_token_mint: *governing_token_mint_info.key,
governance_delegate: None,
unrelinquished_votes_count: 0,
total_votes_count: 0,
outstanding_proposal_count: 0,
reserved: [0; 7],
};
create_and_serialize_account_signed(
payer_info,
token_owner_record_info,
&token_owner_record_data,
&get_token_owner_record_address_seeds(
realm_info.key,
governing_token_mint_info.key,
governing_token_owner_info.key,
),
program_id,
system_info,
&rent,
)
}

View File

@ -50,6 +50,8 @@ pub fn process_deposit_governing_tokens(
let realm_data = get_realm_data(program_id, realm_info)?;
let governing_token_mint = get_spl_token_mint(governing_token_holding_info)?;
realm_data.asset_governing_tokens_deposits_allowed(&governing_token_mint)?;
realm_data.assert_is_valid_governing_token_mint_and_holding(
program_id,
realm_info.key,

View File

@ -5,18 +5,27 @@ use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
};
use crate::{
error::GovernanceError,
state::realm::{assert_valid_realm_config_args, get_realm_data_for_authority, RealmConfigArgs},
state::{
enums::GovernanceAccountType,
realm::{assert_valid_realm_config_args, get_realm_data_for_authority, RealmConfigArgs},
realm_config::{
get_realm_config_address_seeds, get_realm_config_data_for_realm, RealmConfigAccount,
},
},
tools::account::create_and_serialize_account_signed,
};
/// Processes SetRealmConfig instruction
pub fn process_set_realm_config(
program_id: &Pubkey,
accounts: &[AccountInfo],
config_args: RealmConfigArgs,
realm_config_args: RealmConfigArgs,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -30,10 +39,12 @@ pub fn process_set_realm_config(
return Err(GovernanceError::RealmAuthorityMustSign.into());
}
assert_valid_realm_config_args(&config_args)?;
assert_valid_realm_config_args(&realm_config_args)?;
if config_args.use_council_mint {
let council_token_mint_info = next_account_info(account_info_iter)?;
// Setup council
if realm_config_args.use_council_mint {
let council_token_mint_info = next_account_info(account_info_iter)?; // 2
let _council_token_holding_info = next_account_info(account_info_iter)?; // 3
// Council mint can only be at present set to none (removed) and changing it to other mint is not supported
// It might be implemented in future versions but it needs careful planning
@ -53,10 +64,56 @@ pub fn process_set_realm_config(
realm_data.config.council_mint = None;
}
let system_info = next_account_info(account_info_iter)?; // 4
let realm_config_info = next_account_info(account_info_iter)?; // 5
// Setup community voter weight addin
if realm_config_args.use_community_voter_weight_addin {
let payer_info = next_account_info(account_info_iter)?; // 6
let community_voter_weight_addin_info = next_account_info(account_info_iter)?; // 7
if realm_config_info.data_is_empty() {
let realm_config_data = RealmConfigAccount {
account_type: GovernanceAccountType::RealmConfig,
realm: *realm_info.key,
community_voter_weight_addin: Some(*community_voter_weight_addin_info.key),
reserved_1: None,
reserved_2: None,
reserved_3: None,
reserved: [0; 128],
};
let rent = Rent::get().unwrap();
create_and_serialize_account_signed::<RealmConfigAccount>(
payer_info,
realm_config_info,
&realm_config_data,
&get_realm_config_address_seeds(realm_info.key),
program_id,
system_info,
&rent,
)?;
} else {
let mut realm_config_data =
get_realm_config_data_for_realm(program_id, realm_config_info, realm_info.key)?;
realm_config_data.community_voter_weight_addin =
Some(*community_voter_weight_addin_info.key);
realm_config_data.serialize(&mut *realm_config_info.data.borrow_mut())?;
}
} else if realm_data.config.use_community_voter_weight_addin {
let mut realm_config_data =
get_realm_config_data_for_realm(program_id, realm_config_info, realm_info.key)?;
realm_config_data.community_voter_weight_addin = None;
realm_config_data.serialize(&mut *realm_config_info.data.borrow_mut())?;
}
realm_data.config.community_mint_max_vote_weight_source =
config_args.community_mint_max_vote_weight_source;
realm_config_args.community_mint_max_vote_weight_source;
realm_data.config.min_community_tokens_to_create_governance =
config_args.min_community_tokens_to_create_governance;
realm_config_args.min_community_tokens_to_create_governance;
realm_data.config.use_community_voter_weight_addin =
realm_config_args.use_community_voter_weight_addin;
realm_data.serialize(&mut *realm_info.data.borrow_mut())?;

View File

@ -58,13 +58,7 @@ pub fn process_withdraw_governing_tokens(
&token_owner_record_address_seeds,
)?;
if token_owner_record_data.unrelinquished_votes_count > 0 {
return Err(GovernanceError::AllVotesMustBeRelinquishedToWithdrawGoverningTokens.into());
}
if token_owner_record_data.outstanding_proposal_count > 0 {
return Err(GovernanceError::AllProposalsMustBeFinalisedToWithdrawGoverningTokens.into());
}
token_owner_record_data.assert_can_withdraw_governing_tokens()?;
transfer_spl_tokens_signed(
governing_token_holding_info,

View File

@ -38,6 +38,9 @@ pub enum GovernanceAccountType {
/// Token Governance account
TokenGovernance,
/// Realm config account
RealmConfig,
}
impl Default for GovernanceAccountType {

View File

@ -5,6 +5,7 @@ pub mod governance;
pub mod proposal;
pub mod proposal_instruction;
pub mod realm;
pub mod realm_config;
pub mod signatory_record;
pub mod token_owner_record;
pub mod vote_record;

View File

@ -565,7 +565,8 @@ mod test {
name: "test-realm".to_string(),
config: RealmConfig {
council_mint: Some(Pubkey::new_unique()),
reserved: [0; 8],
reserved: [0; 7],
use_community_voter_weight_addin: false,
community_mint_max_vote_weight_source:
MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION,

View File

@ -26,14 +26,21 @@ pub struct RealmConfigArgs {
/// The source used for community mint max vote weight source
pub community_mint_max_vote_weight_source: MintMaxVoteWeightSource,
/// Indicates whether an external addin program should be used to provide community voters weights
/// If yes then the voters weight program account must be passed to the instruction
pub use_community_voter_weight_addin: bool,
}
/// 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
pub use_community_voter_weight_addin: bool,
/// Reserved space for future versions
pub reserved: [u8; 8],
pub reserved: [u8; 7],
/// Min number of community tokens required to create a governance
pub min_community_tokens_to_create_governance: u64,
@ -118,6 +125,21 @@ impl Realm {
Ok(())
}
/// Asserts the given governing token can be deposited into the realm
pub fn asset_governing_tokens_deposits_allowed(
&self,
governing_token_mint: &Pubkey,
) -> Result<(), ProgramError> {
// If the deposit is for the community token and the realm uses community voter weight addin then panic
if self.config.use_community_voter_weight_addin
&& self.community_mint == *governing_token_mint
{
return Err(GovernanceError::GoverningTokenDepositsNotAllowed.into());
}
Ok(())
}
}
/// Checks whether realm account exists, is initialized and owned by Governance program
@ -222,6 +244,9 @@ pub fn assert_valid_realm_config_args(config_args: &RealmConfigArgs) -> Result<(
#[cfg(test)]
mod test {
use crate::instruction::GovernanceInstruction;
use solana_program::borsh::try_from_slice_unchecked;
use super::*;
#[test]
@ -235,7 +260,8 @@ mod test {
name: "test-realm".to_string(),
config: RealmConfig {
council_mint: Some(Pubkey::new_unique()),
reserved: [0; 8],
use_community_voter_weight_addin: false,
reserved: [0; 7],
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::Absolute(100),
min_community_tokens_to_create_governance: 10,
@ -246,4 +272,76 @@ mod test {
assert_eq!(realm.get_max_size(), Some(size));
}
#[test]
fn test_deserialize_v2_realm_account_from_v1() {
// Arrange
let realm_v1 = spl_governance_v1::state::realm::Realm {
account_type: spl_governance_v1::state::enums::GovernanceAccountType::Realm,
community_mint: Pubkey::new_unique(),
config: spl_governance_v1::state::realm::RealmConfig {
council_mint: Some(Pubkey::new_unique()),
reserved: [0; 8],
community_mint_max_vote_weight_source:
spl_governance_v1::state::enums::MintMaxVoteWeightSource::Absolute(100),
min_community_tokens_to_create_governance: 10,
},
reserved: [0; 8],
authority: Some(Pubkey::new_unique()),
name: "test-realm-v1".to_string(),
};
let mut realm_v1_data = vec![];
realm_v1.serialize(&mut realm_v1_data).unwrap();
// Act
let realm_v2: Realm = try_from_slice_unchecked(&realm_v1_data).unwrap();
// Assert
assert!(!realm_v2.config.use_community_voter_weight_addin);
assert_eq!(realm_v2.account_type, GovernanceAccountType::Realm);
assert_eq!(
realm_v2.config.min_community_tokens_to_create_governance,
realm_v1.config.min_community_tokens_to_create_governance,
);
}
#[test]
fn test_deserialize_v1_create_realm_instruction_from_v2() {
// Arrange
let create_realm_ix = GovernanceInstruction::CreateRealm {
name: "test-realm".to_string(),
config_args: RealmConfigArgs {
use_council_mint: true,
min_community_tokens_to_create_governance: 100,
community_mint_max_vote_weight_source:
MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION,
use_community_voter_weight_addin: false,
},
};
let mut create_realm_ix_data = vec![];
create_realm_ix
.serialize(&mut create_realm_ix_data)
.unwrap();
// Act
let create_realm_ix_v1: spl_governance_v1::instruction::GovernanceInstruction =
try_from_slice_unchecked(&create_realm_ix_data).unwrap();
// Assert
if let spl_governance_v1::instruction::GovernanceInstruction::CreateRealm {
name,
config_args,
} = create_realm_ix_v1
{
assert_eq!("test-realm", name);
assert_eq!(
spl_governance_v1::state::enums::MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION,
config_args.community_mint_max_vote_weight_source
);
} else {
panic!("Can't deserialize v1 CreateRealm instruction from v2");
}
}
}

View File

@ -0,0 +1,108 @@
//! RealmConfig account
use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use crate::{
error::GovernanceError,
state::enums::GovernanceAccountType,
tools::account::{get_account_data, AccountMaxSize},
};
/// RealmConfig account
/// The account is an optional extension to RealmConfig stored on Realm account
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfigAccount {
/// Governance account type
pub account_type: GovernanceAccountType,
/// The realm the config belong to
pub realm: Pubkey,
/// Addin providing voter weights for community token
pub community_voter_weight_addin: Option<Pubkey>,
/// Reserved for community max vote weight addin
pub reserved_1: Option<Pubkey>,
/// Reserved for council voter weight addin
pub reserved_2: Option<Pubkey>,
/// Reserved for council max vote weight addin
pub reserved_3: Option<Pubkey>,
/// Reserved
pub reserved: [u8; 128],
}
impl AccountMaxSize for RealmConfigAccount {
fn get_max_size(&self) -> Option<usize> {
Some(1 + 32 + 33 * 4 + 128)
}
}
impl IsInitialized for RealmConfigAccount {
fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::RealmConfig
}
}
/// Deserializes RealmConfig account and checks owner program
pub fn get_realm_config_data(
program_id: &Pubkey,
realm_config_info: &AccountInfo,
) -> Result<RealmConfigAccount, ProgramError> {
get_account_data::<RealmConfigAccount>(realm_config_info, program_id)
}
/// Deserializes RealmConfig account and checks the owner program and the Realm it belongs to
pub fn get_realm_config_data_for_realm(
program_id: &Pubkey,
realm_config_info: &AccountInfo,
realm: &Pubkey,
) -> Result<RealmConfigAccount, ProgramError> {
let realm_config_data = get_realm_config_data(program_id, realm_config_info)?;
if realm_config_data.realm != *realm {
return Err(GovernanceError::InvalidRealmConfigForRealm.into());
}
Ok(realm_config_data)
}
/// Returns RealmConfig PDA seeds
pub fn get_realm_config_address_seeds(realm: &Pubkey) -> [&[u8]; 2] {
[b"realm-config", realm.as_ref()]
}
/// Returns RealmConfig PDA address
pub fn get_realm_config_address(program_id: &Pubkey, realm: &Pubkey) -> Pubkey {
Pubkey::find_program_address(&get_realm_config_address_seeds(realm), program_id).0
}
#[cfg(test)]
mod test {
use super::*;
use crate::state::{enums::GovernanceAccountType, realm_config::RealmConfigAccount};
#[test]
fn test_max_size() {
let realm_config = RealmConfigAccount {
account_type: GovernanceAccountType::Realm,
realm: Pubkey::new_unique(),
community_voter_weight_addin: Some(Pubkey::new_unique()),
reserved_1: Some(Pubkey::new_unique()),
reserved_2: Some(Pubkey::new_unique()),
reserved_3: Some(Pubkey::new_unique()),
reserved: [0; 128],
};
let size = realm_config.try_to_vec().unwrap().len();
assert_eq!(realm_config.get_max_size(), Some(size));
}
}

View File

@ -1,15 +1,23 @@
//! Token Owner Record Account
use std::slice::Iter;
use crate::{
addins::voter_weight::get_voter_weight_record_data_for_token_owner_record,
error::GovernanceError,
state::{enums::GovernanceAccountType, governance::GovernanceConfig, realm::Realm},
state::{
enums::GovernanceAccountType, governance::GovernanceConfig, realm::Realm,
realm_config::get_realm_config_data_for_realm,
},
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED,
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
account_info::{next_account_info, AccountInfo},
program_error::ProgramError,
program_pack::IsInitialized,
pubkey::Pubkey,
};
@ -95,8 +103,9 @@ impl TokenOwnerRecord {
&self,
realm_data: &Realm,
config: &GovernanceConfig,
voter_weight: u64,
) -> Result<(), ProgramError> {
let min_tokens_to_create_proposal =
let min_weight_to_create_proposal =
if self.governing_token_mint == realm_data.community_mint {
config.min_community_tokens_to_create_proposal
} else if Some(self.governing_token_mint) == realm_data.config.council_mint {
@ -105,11 +114,11 @@ impl TokenOwnerRecord {
return Err(GovernanceError::InvalidGoverningTokenMint.into());
};
if self.governing_token_deposit_amount < min_tokens_to_create_proposal {
if voter_weight < min_weight_to_create_proposal {
return Err(GovernanceError::NotEnoughTokensToCreateProposal.into());
}
// The number of outstanding proposals is currently restricted to 1
// The number of outstanding proposals is currently restricted to 10
// If there is a need to change it in the future then it should be added to realm or governance config
if self.outstanding_proposal_count >= 10 {
return Err(GovernanceError::TooManyOutstandingProposals.into());
@ -119,8 +128,12 @@ impl TokenOwnerRecord {
}
/// Asserts TokenOwner has enough tokens to be allowed to create governance
pub fn assert_can_create_governance(&self, realm_data: &Realm) -> Result<(), ProgramError> {
let min_tokens_to_create_governance =
pub fn assert_can_create_governance(
&self,
realm_data: &Realm,
voter_weight: u64,
) -> Result<(), ProgramError> {
let min_weight_to_create_governance =
if self.governing_token_mint == realm_data.community_mint {
realm_data.config.min_community_tokens_to_create_governance
} else if Some(self.governing_token_mint) == realm_data.config.council_mint {
@ -130,13 +143,30 @@ impl TokenOwnerRecord {
return Err(GovernanceError::InvalidGoverningTokenMint.into());
};
if self.governing_token_deposit_amount < min_tokens_to_create_governance {
if voter_weight < min_weight_to_create_governance {
return Err(GovernanceError::NotEnoughTokensToCreateGovernance.into());
}
Ok(())
}
/// Asserts TokenOwner can withdraw tokens from Realm
pub fn assert_can_withdraw_governing_tokens(&self) -> Result<(), ProgramError> {
if self.unrelinquished_votes_count > 0 {
return Err(
GovernanceError::AllVotesMustBeRelinquishedToWithdrawGoverningTokens.into(),
);
}
if self.outstanding_proposal_count > 0 {
return Err(
GovernanceError::AllProposalsMustBeFinalisedToWithdrawGoverningTokens.into(),
);
}
Ok(())
}
/// Decreases outstanding_proposal_count
pub fn decrease_outstanding_proposal_count(&mut self) {
// Previous versions didn't use the count and it can be already 0
@ -146,6 +176,36 @@ impl TokenOwnerRecord {
self.outstanding_proposal_count.checked_sub(1).unwrap();
}
}
/// Resolves voter's weight using either the amount deposited into the realm or weight provided by voter weight addin (if configured)
pub fn resolve_voter_weight(
&self,
program_id: &Pubkey,
account_info_iter: &mut Iter<AccountInfo>,
realm: &Pubkey,
realm_data: &Realm,
) -> Result<u64, ProgramError> {
// if the realm uses addin for community voter weight then use the externally provided weight
if realm_data.config.use_community_voter_weight_addin
&& realm_data.community_mint == self.governing_token_mint
{
let realm_config_info = next_account_info(account_info_iter)?;
let voter_weight_record_info = next_account_info(account_info_iter)?;
let realm_config_data =
get_realm_config_data_for_realm(program_id, realm_config_info, realm)?;
let voter_weight_record_data = get_voter_weight_record_data_for_token_owner_record(
&realm_config_data.community_voter_weight_addin.unwrap(),
voter_weight_record_info,
self,
)?;
voter_weight_record_data.assert_is_up_to_date()?;
Ok(voter_weight_record_data.voter_weight)
} else {
Ok(self.governing_token_deposit_amount)
}
}
}
/// Returns TokenOwnerRecord PDA address

Binary file not shown.

View File

@ -18,7 +18,8 @@ async fn test_add_signatory() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -64,7 +65,8 @@ async fn test_add_signatory_with_owner_or_delegate_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -82,7 +84,8 @@ async fn test_add_signatory_with_owner_or_delegate_must_sign_error() {
let other_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.token_owner = other_token_owner_record_cookie.token_owner;
@ -110,7 +113,8 @@ async fn test_add_signatory_with_invalid_proposal_owner_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -128,7 +132,8 @@ async fn test_add_signatory_with_invalid_proposal_owner_error() {
let other_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.address = other_token_owner_record_cookie.address;

View File

@ -17,7 +17,8 @@ async fn test_cancel_proposal() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -66,7 +67,8 @@ async fn test_cancel_proposal_with_already_completed_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -112,7 +114,8 @@ async fn test_cancel_proposal_with_owner_or_delegate_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -130,7 +133,8 @@ async fn test_cancel_proposal_with_owner_or_delegate_must_sign_error() {
let token_owner_record_cookie2 = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.token_owner = token_owner_record_cookie2.token_owner;

View File

@ -21,7 +21,8 @@ async fn test_cast_vote() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -98,7 +99,8 @@ async fn test_cast_vote_with_invalid_governance_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -148,7 +150,8 @@ async fn test_cast_vote_with_invalid_mint_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -188,7 +191,8 @@ async fn test_cast_vote_with_invalid_token_owner_record_mint_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -207,7 +211,8 @@ async fn test_cast_vote_with_invalid_token_owner_record_mint_error() {
// Try to use token_owner_record for Council Mint with Community Proposal
let token_owner_record_cookie2 = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.address = token_owner_record_cookie2.address;
@ -234,7 +239,8 @@ async fn test_cast_vote_with_invalid_token_owner_record_from_different_realm_err
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -255,7 +261,8 @@ async fn test_cast_vote_with_invalid_token_owner_record_from_different_realm_err
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie2)
.await;
.await
.unwrap();
token_owner_record_cookie.address = token_owner_record_cookie2.address;
@ -279,7 +286,8 @@ async fn test_cast_vote_with_governance_authority_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -298,7 +306,8 @@ async fn test_cast_vote_with_governance_authority_must_sign_error() {
// Try to use a different owner to sign
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.token_owner = token_owner_record_cookie2.token_owner;
@ -325,7 +334,8 @@ async fn test_cast_vote_with_vote_tipped_to_succeeded() {
let token_owner_record_cookie1 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -338,11 +348,13 @@ async fn test_cast_vote_with_vote_tipped_to_succeeded() {
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let token_owner_record_cookie3 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governance_test
.mint_community_tokens(&realm_cookie, 20)
@ -413,7 +425,8 @@ async fn test_cast_vote_with_vote_tipped_to_defeated() {
// 100 votes
let token_owner_record_cookie1 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -427,12 +440,14 @@ async fn test_cast_vote_with_vote_tipped_to_defeated() {
// 100 votes
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// 100 votes
let token_owner_record_cookie3 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Total 320 votes
governance_test
@ -507,7 +522,8 @@ async fn test_cast_vote_with_threshold_below_50_and_vote_not_tipped() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance_using_config(
@ -560,7 +576,8 @@ async fn test_cast_vote_with_voting_time_expired_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -610,7 +627,8 @@ async fn test_cast_vote_with_cast_twice_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(

View File

@ -16,7 +16,8 @@ async fn test_create_account_governance() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
let account_governance_cookie = governance_test
@ -49,7 +50,8 @@ async fn test_create_account_governance_with_invalid_realm_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let account_governance_cookie = governance_test
.with_account_governance(
@ -88,7 +90,8 @@ async fn test_create_account_governance_with_invalid_config_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Arrange
let mut config = governance_test.get_default_governance_config();
@ -144,7 +147,8 @@ async fn test_create_account_governance_with_not_enough_community_tokens_error()
let token_owner_record_cookie = governance_test
.with_community_token_deposit_amount(&realm_cookie, token_amount)
.await;
.await
.unwrap();
// Act
let err = governance_test
@ -177,7 +181,8 @@ async fn test_create_account_governance_with_not_enough_council_tokens_error() {
let token_owner_record_cookie = governance_test
.with_council_token_deposit_amount(&realm_cookie, token_amount)
.await;
.await
.unwrap();
// Act
let err = governance_test

View File

@ -18,7 +18,8 @@ async fn test_create_mint_governance() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
let mint_governance_cookie = governance_test
@ -57,7 +58,8 @@ async fn test_create_mint_governance_without_transferring_mint_authority() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_mint_cookie.transfer_mint_authority = false;
// Act
@ -98,7 +100,8 @@ async fn test_create_mint_governance_without_transferring_mint_authority_with_in
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_mint_cookie.transfer_mint_authority = false;
governed_mint_cookie.mint_authority = Keypair::new();
@ -129,7 +132,8 @@ async fn test_create_mint_governance_without_transferring_mint_authority_with_au
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_mint_cookie.transfer_mint_authority = false;
@ -142,7 +146,7 @@ async fn test_create_mint_governance_without_transferring_mint_authority_with_au
|i| {
i.accounts[3].is_signer = false; // governed_mint_authority
},
Some(&[]),
Some(&[&token_owner_record_cookie.token_owner]),
)
.await
.err()
@ -162,7 +166,8 @@ async fn test_create_mint_governance_with_invalid_mint_authority_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_mint_cookie.mint_authority = Keypair::new();
@ -191,7 +196,8 @@ async fn test_create_mint_governance_with_invalid_realm_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mint_governance_cookie = governance_test
.with_mint_governance(

View File

@ -20,7 +20,8 @@ async fn test_create_program_governance() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
let program_governance_cookie = governance_test
@ -61,7 +62,8 @@ async fn test_create_program_governance_without_transferring_upgrade_authority()
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_program_cookie.transfer_upgrade_authority = false;
@ -108,7 +110,8 @@ async fn test_create_program_governance_without_transferring_upgrade_authority_w
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_program_cookie.transfer_upgrade_authority = false;
governed_program_cookie.upgrade_authority = Keypair::new();
@ -139,7 +142,8 @@ async fn test_create_program_governance_without_transferring_upgrade_authority_w
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_program_cookie.transfer_upgrade_authority = false;
@ -152,7 +156,7 @@ async fn test_create_program_governance_without_transferring_upgrade_authority_w
|i| {
i.accounts[4].is_signer = false; // governed_program_upgrade_authority
},
Some(&[]),
Some(&[&token_owner_record_cookie.token_owner]),
)
.await
.err()
@ -172,7 +176,8 @@ async fn test_create_program_governance_with_incorrect_upgrade_authority_error()
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_program_cookie.upgrade_authority = Keypair::new();
@ -201,7 +206,8 @@ async fn test_create_program_governance_with_invalid_realm_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let program_governance_cookie = governance_test
.with_program_governance(

View File

@ -19,7 +19,8 @@ async fn test_create_community_proposal() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -66,7 +67,8 @@ async fn test_create_multiple_proposals() {
let community_token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -79,7 +81,8 @@ async fn test_create_multiple_proposals() {
let council_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
let community_proposal_cookie = governance_test
@ -131,7 +134,8 @@ async fn test_create_proposal_with_not_authorized_governance_authority_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -168,7 +172,8 @@ async fn test_create_proposal_with_governance_delegate_signer() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -210,7 +215,8 @@ async fn test_create_proposal_with_not_enough_community_tokens_error() {
let token_owner_record_cookie1 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -226,7 +232,8 @@ async fn test_create_proposal_with_not_enough_community_tokens_error() {
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit_amount(&realm_cookie, token_amount)
.await;
.await
.unwrap();
// Act
let err = governance_test
@ -252,7 +259,8 @@ async fn test_create_proposal_with_not_enough_council_tokens_error() {
let token_owner_record_cookie = governance_test
.with_council_token_deposit_amount(&realm_cookie, token_amount)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -284,7 +292,8 @@ async fn test_create_proposal_with_owner_or_delegate_must_sign_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -297,7 +306,8 @@ async fn test_create_proposal_with_owner_or_delegate_must_sign_error() {
let council_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
let err = governance_test
@ -331,7 +341,8 @@ async fn test_create_proposal_with_invalid_governing_token_mint_error() {
let mut token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -366,7 +377,8 @@ async fn test_create_community_proposal_using_council_tokens() {
let mut community_token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -379,7 +391,8 @@ async fn test_create_community_proposal_using_council_tokens() {
let council_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Change the proposal owner to council token owner
community_token_owner_record_cookie.address = council_token_owner_record_cookie.address;
@ -420,7 +433,8 @@ async fn test_create_council_proposal_using_community_tokens() {
let mut council_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -433,7 +447,8 @@ async fn test_create_council_proposal_using_community_tokens() {
let community_token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Change the proposal owner to community token owner
council_token_owner_record_cookie.address = community_token_owner_record_cookie.address;

View File

@ -33,6 +33,7 @@ async fn test_create_realm_with_non_default_config() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(1),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
// Act

View File

@ -18,7 +18,8 @@ async fn test_create_token_governance() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
let token_governance_cookie = governance_test
@ -54,7 +55,8 @@ async fn test_create_token_governance_without_transferring_token_owner() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_token_cookie.transfer_token_owner = false;
@ -96,7 +98,8 @@ async fn test_create_token_governance_without_transferring_token_owner_with_inva
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_token_cookie.transfer_token_owner = false;
governed_token_cookie.token_owner = Keypair::new();
@ -127,7 +130,8 @@ async fn test_create_token_governance_without_transferring_token_owner_with_owne
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_token_cookie.transfer_token_owner = false;
@ -140,7 +144,7 @@ async fn test_create_token_governance_without_transferring_token_owner_with_owne
|i| {
i.accounts[3].is_signer = false; // governed_token_owner
},
Some(&[]),
Some(&[&token_owner_record_cookie.token_owner]),
)
.await
.err()
@ -160,7 +164,8 @@ async fn test_create_token_governance_with_invalid_token_owner_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governed_token_cookie.token_owner = Keypair::new();
@ -189,7 +194,8 @@ async fn test_create_token_governance_with_invalid_realm_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let token_governance_cookie = governance_test
.with_token_governance(

View File

@ -0,0 +1,29 @@
#![cfg(feature = "test-bpf")]
use solana_program_test::*;
mod program_test;
use program_test::*;
#[tokio::test]
async fn test_create_token_owner_record() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
// Act
let token_owner_record_cookie = governance_test.with_token_owner_record(&realm_cookie).await;
// Assert
let token_owner_record_account = governance_test
.get_token_owner_record_account(&token_owner_record_cookie.address)
.await;
assert_eq!(0, token_owner_record_account.governing_token_deposit_amount);
assert_eq!(
token_owner_record_cookie.account,
token_owner_record_account
);
}

View File

@ -18,7 +18,8 @@ async fn test_deposit_initial_community_tokens() {
// Act
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Assert
@ -61,7 +62,8 @@ async fn test_deposit_initial_council_tokens() {
// Act
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Assert
let token_owner_record = governance_test
@ -100,7 +102,8 @@ async fn test_deposit_subsequent_community_tokens() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let deposit_amount = 5;
let total_deposit_amount = token_owner_record_cookie
@ -146,7 +149,8 @@ async fn test_deposit_subsequent_council_tokens() {
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let deposit_amount = 5;
let total_deposit_amount = token_owner_record_cookie
@ -283,7 +287,8 @@ async fn test_deposit_community_tokens_with_malicious_holding_account_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governance_test
.bench

View File

@ -1,4 +1,4 @@
#![cfg(feature = "test-bpf-all")]
#![cfg(feature = "test-bpf")]
mod program_test;
@ -26,7 +26,8 @@ async fn test_execute_mint_instruction() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut mint_governance_cookie = governance_test
.with_mint_governance(
@ -72,7 +73,7 @@ async fn test_execute_mint_instruction() {
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
let clock = governance_test.get_clock().await;
let clock = governance_test.bench.get_clock().await;
// Act
governance_test
@ -122,7 +123,8 @@ async fn test_execute_transfer_instruction() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut token_governance_cookie = governance_test
.with_token_governance(
@ -168,7 +170,7 @@ async fn test_execute_transfer_instruction() {
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
.await;
let clock = governance_test.get_clock().await;
let clock = governance_test.bench.get_clock().await;
// Act
governance_test
@ -218,7 +220,8 @@ async fn test_execute_upgrade_program_instruction() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut program_governance_cookie = governance_test
.with_program_governance(
@ -275,6 +278,7 @@ async fn test_execute_upgrade_program_instruction() {
);
let err = governance_test
.bench
.process_transaction(&[governed_program_instruction.clone()], None)
.await
.err()
@ -283,7 +287,7 @@ async fn test_execute_upgrade_program_instruction() {
// solana_bpf_rust_upgradable returns CustomError == 42
assert_eq!(ProgramError::Custom(42), err);
let clock = governance_test.get_clock().await;
let clock = governance_test.bench.get_clock().await;
// Act
governance_test
@ -321,6 +325,7 @@ async fn test_execute_upgrade_program_instruction() {
governance_test.advance_clock().await;
let err = governance_test
.bench
.process_transaction(&[governed_program_instruction.clone()], None)
.await
.err()
@ -342,7 +347,8 @@ async fn test_execute_instruction_with_invalid_state_errors() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut mint_governance_cookie = governance_test
.with_mint_governance(
@ -504,7 +510,8 @@ async fn test_execute_instruction_for_other_proposal_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut mint_governance_cookie = governance_test
.with_mint_governance(
@ -553,13 +560,16 @@ async fn test_execute_instruction_for_other_proposal_error() {
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let proposal_cookie2 = governance_test
.with_proposal(&token_owner_record_cookie2, &mut mint_governance_cookie)
.await
.unwrap();
governance_test.advance_clock().await;
// Act
let err = governance_test
.execute_instruction(&proposal_cookie2, &proposal_instruction_cookie)
@ -584,7 +594,8 @@ async fn test_execute_mint_instruction_twice_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut mint_governance_cookie = governance_test
.with_mint_governance(

View File

@ -25,7 +25,8 @@ async fn test_finalize_vote_to_succeeded() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance_using_config(
@ -117,7 +118,8 @@ async fn test_finalize_vote_to_defeated() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -184,7 +186,8 @@ async fn test_finalize_vote_with_invalid_mint_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -243,7 +246,8 @@ async fn test_finalize_vote_with_invalid_governance_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(

View File

@ -21,7 +21,8 @@ async fn test_execute_flag_instruction_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -107,7 +108,8 @@ async fn test_execute_instruction_after_flagged_with_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut mint_governance_cookie = governance_test
.with_mint_governance(
@ -196,7 +198,8 @@ async fn test_execute_second_instruction_after_first_instruction_flagged_with_er
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut mint_governance_cookie = governance_test
.with_mint_governance(
@ -281,7 +284,8 @@ async fn test_flag_instruction_error_with_instruction_already_executed_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut mint_governance_cookie = governance_test
.with_mint_governance(
@ -365,7 +369,8 @@ async fn test_flag_instruction_error_with_owner_or_delegate_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -408,7 +413,8 @@ async fn test_flag_instruction_error_with_owner_or_delegate_must_sign_error() {
let token_owner_record_cookie2 = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Try to maliciously sign using different owner signature
token_owner_record_cookie.token_owner = token_owner_record_cookie2.token_owner;

View File

@ -17,7 +17,8 @@ async fn test_insert_instruction() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -69,7 +70,8 @@ async fn test_insert_multiple_instructions() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -117,7 +119,8 @@ async fn test_insert_instruction_with_invalid_index_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -154,7 +157,8 @@ async fn test_insert_instruction_with_instruction_already_exists_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -202,7 +206,8 @@ async fn test_insert_instruction_with_invalid_hold_up_time_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance_using_config(
@ -242,7 +247,8 @@ async fn test_insert_instruction_with_not_editable_proposal_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -282,7 +288,8 @@ async fn test_insert_instruction_with_owner_or_delegate_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -300,7 +307,8 @@ async fn test_insert_instruction_with_owner_or_delegate_must_sign_error() {
let token_owner_record_cookie2 = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.token_owner = token_owner_record_cookie2.token_owner;
@ -328,7 +336,8 @@ async fn test_insert_instruction_with_invalid_governance_for_proposal_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(

View File

@ -18,7 +18,8 @@ async fn test_relinquish_voted_proposal() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -79,7 +80,8 @@ async fn test_relinquish_active_yes_vote() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -146,7 +148,8 @@ async fn test_relinquish_active_no_vote() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -213,7 +216,8 @@ async fn test_relinquish_vote_with_invalid_mint_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -259,7 +263,8 @@ async fn test_relinquish_vote_with_governance_authority_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -288,7 +293,8 @@ async fn test_relinquish_vote_with_governance_authority_must_sign_error() {
// Try to use a different owner to sign
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.token_owner = token_owner_record_cookie2.token_owner;
@ -318,7 +324,8 @@ async fn test_relinquish_vote_with_invalid_vote_record_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -331,7 +338,8 @@ async fn test_relinquish_vote_with_invalid_vote_record_error() {
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Total 400 tokens
governance_test
@ -382,7 +390,8 @@ async fn test_relinquish_vote_with_already_relinquished_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(

View File

@ -17,7 +17,8 @@ async fn test_remove_instruction() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -77,7 +78,8 @@ async fn test_replace_instruction() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -147,7 +149,8 @@ async fn test_remove_front_instruction() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -210,7 +213,8 @@ async fn test_remove_instruction_with_owner_or_delegate_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -233,7 +237,8 @@ async fn test_remove_instruction_with_owner_or_delegate_must_sign_error() {
let token_owner_record_cookie2 = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.token_owner = token_owner_record_cookie2.token_owner;
@ -265,7 +270,8 @@ async fn test_remove_instruction_with_proposal_not_editable_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -319,7 +325,8 @@ async fn test_remove_instruction_with_instruction_from_other_proposal_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -342,7 +349,8 @@ async fn test_remove_instruction_with_instruction_from_other_proposal_error() {
let token_owner_record_cookie2 = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut proposal_cookie2 = governance_test
.with_proposal(&token_owner_record_cookie2, &mut account_governance_cookie)

View File

@ -18,7 +18,8 @@ async fn test_remove_signatory() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -75,7 +76,8 @@ async fn test_remove_signatory_with_owner_or_delegate_must_sign_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -98,7 +100,8 @@ async fn test_remove_signatory_with_owner_or_delegate_must_sign_error() {
let other_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.token_owner = other_token_owner_record_cookie.token_owner;
@ -130,7 +133,8 @@ async fn test_remove_signatory_with_invalid_proposal_owner_error() {
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -153,7 +157,8 @@ async fn test_remove_signatory_with_invalid_proposal_owner_error() {
let other_token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
token_owner_record_cookie.address = other_token_owner_record_cookie.address;
@ -182,7 +187,8 @@ async fn test_remove_signatory_with_not_editable_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -241,7 +247,8 @@ async fn test_remove_signatory_with_already_signed_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(

View File

@ -22,7 +22,8 @@ async fn test_set_governance_config() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -157,7 +158,8 @@ async fn test_set_governance_config_with_invalid_governance_authority_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(

View File

@ -16,7 +16,8 @@ async fn test_set_community_governance_delegate() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
governance_test
@ -41,7 +42,8 @@ async fn test_set_governance_delegate_to_none() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governance_test
.with_community_governance_delegate(&realm_cookie, &mut token_owner_record_cookie)
@ -73,7 +75,8 @@ async fn test_set_council_governance_delegate() {
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
governance_test
@ -98,7 +101,8 @@ async fn test_set_community_governance_delegate_with_owner_must_sign_error() {
let realm_cookie = governance_test.with_realm().await;
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let hacker_governance_delegate = Keypair::new();
@ -136,7 +140,8 @@ async fn test_set_community_governance_delegate_signed_by_governance_delegate()
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
governance_test
.with_community_governance_delegate(&realm_cookie, &mut token_owner_record_cookie)

View File

@ -23,6 +23,7 @@ async fn test_set_realm_config() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
// Act
@ -52,6 +53,7 @@ async fn test_set_realm_config_with_authority_must_sign_error() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
// Act
@ -83,6 +85,7 @@ async fn test_set_realm_config_with_no_authority_error() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
governance_test
@ -119,6 +122,7 @@ async fn test_set_realm_config_with_invalid_authority_error() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
let realm_cookie2 = governance_test.with_realm().await;
@ -150,6 +154,7 @@ async fn test_set_realm_config_with_remove_council() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
// Act
@ -179,6 +184,7 @@ async fn test_set_realm_config_with_council_change_error() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
// Try to replace council mint
@ -210,6 +216,7 @@ async fn test_set_realm_config_with_council_restore_error() {
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::SupplyFraction(100),
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: false,
};
governance_test

View File

@ -17,7 +17,8 @@ async fn test_sign_off_proposal() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -75,7 +76,8 @@ async fn test_sign_off_proposal_with_signatory_must_sign_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(

View File

@ -22,7 +22,8 @@ async fn test_withdraw_community_tokens() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
governance_test
@ -61,7 +62,8 @@ async fn test_withdraw_council_tokens() {
let token_owner_record_cookie = governance_test
.with_council_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Act
governance_test
@ -100,7 +102,8 @@ async fn test_withdraw_community_tokens_with_owner_must_sign_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let hacker_token_destination = Pubkey::new_unique();
@ -136,7 +139,8 @@ async fn test_withdraw_community_tokens_with_token_owner_record_address_mismatch
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let vote_record_address = get_token_owner_record_address(
&governance_test.program_id,
@ -147,7 +151,8 @@ async fn test_withdraw_community_tokens_with_token_owner_record_address_mismatch
let hacker_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut instruction = withdraw_governing_tokens(
&governance_test.program_id,
@ -185,7 +190,9 @@ async fn test_withdraw_governing_tokens_with_unrelinquished_votes_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
&realm_cookie,
@ -229,7 +236,8 @@ async fn test_withdraw_governing_tokens_after_relinquishing_vote() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
@ -280,7 +288,8 @@ async fn test_withdraw_tokens_with_malicious_holding_account_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
// Try to maliciously withdraw from other token account owned by realm
@ -333,7 +342,9 @@ async fn test_withdraw_governing_tokens_with_outstanding_proposals_error() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
&realm_cookie,
@ -372,7 +383,9 @@ async fn test_withdraw_governing_tokens_after_proposal_cancelled() {
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await;
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
&realm_cookie,

View File

@ -1,9 +1,12 @@
use solana_program::{instruction::Instruction, pubkey::Pubkey};
use solana_sdk::signature::Keypair;
use spl_governance::state::{
governance::Governance, proposal::Proposal, proposal_instruction::ProposalInstruction,
realm::Realm, signatory_record::SignatoryRecord, token_owner_record::TokenOwnerRecord,
vote_record::VoteRecord,
use spl_governance::{
addins::voter_weight::VoterWeightRecord,
state::{
governance::Governance, proposal::Proposal, proposal_instruction::ProposalInstruction,
realm::Realm, realm_config::RealmConfigAccount, signatory_record::SignatoryRecord,
token_owner_record::TokenOwnerRecord, vote_record::VoteRecord,
},
};
use spl_governance_test_sdk::tools::clone_keypair;
@ -27,6 +30,14 @@ pub struct RealmCookie {
pub council_token_holding_account: Option<Pubkey>,
pub realm_authority: Option<Keypair>,
pub realm_config: Option<RealmConfigCookie>,
}
#[derive(Debug)]
pub struct RealmConfigCookie {
pub address: Pubkey,
pub account: RealmConfigAccount,
}
#[derive(Debug)]
@ -44,6 +55,8 @@ pub struct TokenOwnerRecordCookie {
pub governance_authority: Option<Keypair>,
pub governance_delegate: Keypair,
pub voter_weight_record: Option<VoterWeightRecordCookie>,
}
impl TokenOwnerRecordCookie {
@ -145,3 +158,9 @@ pub struct ProposalInstructionCookie {
pub account: ProposalInstruction,
pub instruction: Instruction,
}
#[derive(Debug, Clone)]
pub struct VoterWeightRecordCookie {
pub address: Pubkey,
pub account: VoterWeightRecord,
}

View File

@ -7,6 +7,7 @@ use solana_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack},
pubkey::Pubkey,
system_program,
};
use solana_program_test::*;
@ -14,13 +15,15 @@ use solana_program_test::*;
use solana_sdk::signature::{Keypair, Signer};
use spl_governance::{
addins::voter_weight::{VoterWeightAccountType, VoterWeightRecord},
instruction::{
add_signatory, cancel_proposal, cast_vote, create_account_governance,
create_mint_governance, create_program_governance, create_proposal, create_realm,
create_token_governance, deposit_governing_tokens, execute_instruction, finalize_vote,
flag_instruction_error, insert_instruction, relinquish_vote, remove_instruction,
remove_signatory, set_governance_config, set_governance_delegate, set_realm_authority,
set_realm_config, sign_off_proposal, withdraw_governing_tokens, Vote,
create_token_governance, create_token_owner_record, deposit_governing_tokens,
execute_instruction, finalize_vote, flag_instruction_error, insert_instruction,
relinquish_vote, remove_instruction, remove_signatory, set_governance_config,
set_governance_delegate, set_realm_authority, set_realm_config, sign_off_proposal,
withdraw_governing_tokens, Vote,
},
processor::process_instruction,
state::{
@ -41,6 +44,7 @@ use spl_governance::{
get_governing_token_holding_address, get_realm_address, Realm, RealmConfig,
RealmConfigArgs,
},
realm_config::{get_realm_config_address, RealmConfigAccount},
signatory_record::{get_signatory_record_address, SignatoryRecord},
token_owner_record::{get_token_owner_record_address, TokenOwnerRecord},
vote_record::{get_vote_record_address, VoteRecord},
@ -49,7 +53,10 @@ use spl_governance::{
};
pub mod cookies;
use crate::program_test::cookies::SignatoryRecordCookie;
use crate::program_test::cookies::{
RealmConfigCookie, SignatoryRecordCookie, VoterWeightRecordCookie,
};
use spl_governance_test_sdk::{
tools::{clone_keypair, NopOverride},
@ -66,10 +73,24 @@ pub struct GovernanceProgramTest {
pub bench: ProgramTestBench,
pub next_realm_id: u8,
pub program_id: Pubkey,
pub voter_weight_addin_id: Option<Pubkey>,
}
impl GovernanceProgramTest {
#[allow(dead_code)]
pub async fn start_new() -> Self {
Self::start_impl(false).await
}
#[allow(dead_code)]
pub async fn start_with_voter_weight_addin() -> Self {
Self::start_impl(true).await
}
#[allow(dead_code)]
async fn start_impl(use_voter_weight_addin: bool) -> Self {
let mut programs = vec![];
let program_id = Pubkey::from_str("Governance111111111111111111111111111111111").unwrap();
let program = TestBenchProgram {
@ -78,25 +99,48 @@ impl GovernanceProgramTest {
process_instruction: processor!(process_instruction),
};
let bench = ProgramTestBench::start_new(&[program]).await;
programs.push(program);
let voter_weight_addin_id = if use_voter_weight_addin {
let voter_weight_addin_id =
Pubkey::from_str("VoterWeight11111111111111111111111111111111").unwrap();
let vote_weight_addin = TestBenchProgram {
program_name: "spl_governance_voter_weight_addin",
program_id: voter_weight_addin_id,
process_instruction: None,
};
programs.push(vote_weight_addin);
Some(voter_weight_addin_id)
} else {
None
};
let bench = ProgramTestBench::start_new(&programs).await;
Self {
bench,
next_realm_id: 0,
program_id,
voter_weight_addin_id,
}
}
#[allow(dead_code)]
pub fn get_default_realm_config_args(&mut self) -> RealmConfigArgs {
RealmConfigArgs {
use_council_mint: true,
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION,
min_community_tokens_to_create_governance: 10,
use_community_voter_weight_addin: self.voter_weight_addin_id.is_some(),
}
}
#[allow(dead_code)]
pub async fn with_realm(&mut self) -> RealmCookie {
let config_args = RealmConfigArgs {
use_council_mint: true,
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION,
min_community_tokens_to_create_governance: 10,
};
self.with_realm_using_config_args(&config_args).await
let realm_config_args = self.get_default_realm_config_args();
self.with_realm_using_config_args(&realm_config_args).await
}
#[allow(dead_code)]
@ -157,12 +201,19 @@ impl GovernanceProgramTest {
let realm_authority = Keypair::new();
let community_voter_weight_addin = if config_args.use_community_voter_weight_addin {
self.voter_weight_addin_id
} else {
None
};
let create_realm_instruction = create_realm(
&self.program_id,
&realm_authority.pubkey(),
&community_token_mint_keypair.pubkey(),
&self.bench.payer.pubkey(),
council_token_mint_pubkey,
community_voter_weight_addin,
name.clone(),
config_args.min_community_tokens_to_create_governance,
config_args.community_mint_max_vote_weight_source.clone(),
@ -182,16 +233,34 @@ impl GovernanceProgramTest {
authority: Some(realm_authority.pubkey()),
config: RealmConfig {
council_mint: council_token_mint_pubkey,
reserved: [0; 8],
reserved: [0; 7],
min_community_tokens_to_create_governance: config_args
.min_community_tokens_to_create_governance,
community_mint_max_vote_weight_source: config_args
.community_mint_max_vote_weight_source
.clone(),
use_community_voter_weight_addin: false,
},
};
let realm_config_cookie = if config_args.use_community_voter_weight_addin {
Some(RealmConfigCookie {
address: get_realm_config_address(&self.program_id, &realm_address),
account: RealmConfigAccount {
account_type: GovernanceAccountType::RealmConfig,
realm: realm_address,
community_voter_weight_addin: self.voter_weight_addin_id,
reserved_1: None,
reserved_2: None,
reserved_3: None,
reserved: [0; 128],
},
})
} else {
None
};
RealmCookie {
address: realm_address,
account,
@ -202,6 +271,7 @@ impl GovernanceProgramTest {
council_token_holding_account: council_token_holding_address,
council_mint_authority: council_token_mint_authority,
realm_authority: Some(realm_authority),
realm_config: realm_config_cookie,
}
}
@ -224,6 +294,7 @@ impl GovernanceProgramTest {
&realm_cookie.account.community_mint,
&self.bench.context.payer.pubkey(),
Some(council_mint),
None,
name.clone(),
min_community_tokens_to_create_governance,
community_mint_max_vote_weight_source,
@ -243,11 +314,12 @@ impl GovernanceProgramTest {
authority: Some(realm_authority.pubkey()),
config: RealmConfig {
council_mint: Some(council_mint),
reserved: [0; 8],
reserved: [0; 7],
community_mint_max_vote_weight_source:
MintMaxVoteWeightSource::FULL_SUPPLY_FRACTION,
min_community_tokens_to_create_governance,
use_community_voter_weight_addin: false,
},
};
@ -272,6 +344,7 @@ impl GovernanceProgramTest {
realm_cookie.council_mint_authority.as_ref().unwrap(),
)),
realm_authority: Some(realm_authority),
realm_config: None,
}
}
@ -279,7 +352,7 @@ impl GovernanceProgramTest {
pub async fn with_community_token_deposit(
&mut self,
realm_cookie: &RealmCookie,
) -> TokenOwnerRecordCookie {
) -> Result<TokenOwnerRecordCookie, ProgramError> {
self.with_initial_governing_token_deposit(
&realm_cookie.address,
&realm_cookie.account.community_mint,
@ -289,12 +362,64 @@ impl GovernanceProgramTest {
.await
}
#[allow(dead_code)]
pub async fn with_token_owner_record(
&mut self,
realm_cookie: &RealmCookie,
) -> TokenOwnerRecordCookie {
let token_owner = Keypair::new();
let create_token_owner_record_ix = create_token_owner_record(
&self.program_id,
&realm_cookie.address,
&token_owner.pubkey(),
&realm_cookie.account.community_mint,
&self.bench.payer.pubkey(),
);
self.bench
.process_transaction(&[create_token_owner_record_ix], None)
.await
.unwrap();
let account = TokenOwnerRecord {
account_type: GovernanceAccountType::TokenOwnerRecord,
realm: realm_cookie.address,
governing_token_mint: realm_cookie.account.community_mint,
governing_token_owner: token_owner.pubkey(),
governing_token_deposit_amount: 0,
governance_delegate: None,
unrelinquished_votes_count: 0,
total_votes_count: 0,
outstanding_proposal_count: 0,
reserved: [0; 7],
};
let token_owner_record_address = get_token_owner_record_address(
&self.program_id,
&realm_cookie.address,
&realm_cookie.account.community_mint,
&token_owner.pubkey(),
);
TokenOwnerRecordCookie {
address: token_owner_record_address,
account,
token_source_amount: 0,
token_source: Pubkey::new_unique(),
token_owner,
governance_authority: None,
governance_delegate: Keypair::new(),
voter_weight_record: None,
}
}
#[allow(dead_code)]
pub async fn with_community_token_deposit_amount(
&mut self,
realm_cookie: &RealmCookie,
amount: u64,
) -> TokenOwnerRecordCookie {
) -> Result<TokenOwnerRecordCookie, ProgramError> {
self.with_initial_governing_token_deposit(
&realm_cookie.address,
&realm_cookie.account.community_mint,
@ -343,7 +468,7 @@ impl GovernanceProgramTest {
&mut self,
realm_cookie: &RealmCookie,
amount: u64,
) -> TokenOwnerRecordCookie {
) -> Result<TokenOwnerRecordCookie, ProgramError> {
self.with_initial_governing_token_deposit(
&realm_cookie.address,
&realm_cookie.account.config.council_mint.unwrap(),
@ -357,7 +482,7 @@ impl GovernanceProgramTest {
pub async fn with_council_token_deposit(
&mut self,
realm_cookie: &RealmCookie,
) -> TokenOwnerRecordCookie {
) -> Result<TokenOwnerRecordCookie, ProgramError> {
self.with_initial_governing_token_deposit(
&realm_cookie.address,
&realm_cookie.account.config.council_mint.unwrap(),
@ -374,7 +499,7 @@ impl GovernanceProgramTest {
governing_mint: &Pubkey,
governing_mint_authority: &Keypair,
amount: u64,
) -> TokenOwnerRecordCookie {
) -> Result<TokenOwnerRecordCookie, ProgramError> {
let token_owner = Keypair::new();
let token_source = Keypair::new();
@ -406,8 +531,7 @@ impl GovernanceProgramTest {
&[deposit_governing_tokens_instruction],
Some(&[&token_owner]),
)
.await
.unwrap();
.await?;
let token_owner_record_address = get_token_owner_record_address(
&self.program_id,
@ -431,7 +555,7 @@ impl GovernanceProgramTest {
let governance_delegate = Keypair::from_base58_string(&token_owner.to_base58_string());
TokenOwnerRecordCookie {
Ok(TokenOwnerRecordCookie {
address: token_owner_record_address,
account,
@ -440,7 +564,8 @@ impl GovernanceProgramTest {
token_owner,
governance_authority: None,
governance_delegate,
}
voter_weight_record: None,
})
}
#[allow(dead_code)]
@ -622,9 +747,9 @@ impl GovernanceProgramTest {
pub async fn set_realm_config(
&mut self,
realm_cookie: &mut RealmCookie,
config_args: &RealmConfigArgs,
realm_config_args: &RealmConfigArgs,
) -> Result<(), ProgramError> {
self.set_realm_config_using_instruction(realm_cookie, config_args, NopOverride, None)
self.set_realm_config_using_instruction(realm_cookie, realm_config_args, NopOverride, None)
.await
}
@ -632,23 +757,33 @@ impl GovernanceProgramTest {
pub async fn set_realm_config_using_instruction<F: Fn(&mut Instruction)>(
&mut self,
realm_cookie: &mut RealmCookie,
config_args: &RealmConfigArgs,
realm_config_args: &RealmConfigArgs,
instruction_override: F,
signers_override: Option<&[&Keypair]>,
) -> Result<(), ProgramError> {
let council_token_mint = if config_args.use_council_mint {
let council_token_mint = if realm_config_args.use_council_mint {
realm_cookie.account.config.council_mint
} else {
None
};
let community_voter_weight_addin = if realm_config_args.use_community_voter_weight_addin {
self.voter_weight_addin_id
} else {
None
};
let mut set_realm_config_ix = set_realm_config(
&self.program_id,
&realm_cookie.address,
&realm_cookie.realm_authority.as_ref().unwrap().pubkey(),
council_token_mint,
config_args.min_community_tokens_to_create_governance,
config_args.community_mint_max_vote_weight_source.clone(),
&self.bench.payer.pubkey(),
community_voter_weight_addin,
realm_config_args.min_community_tokens_to_create_governance,
realm_config_args
.community_mint_max_vote_weight_source
.clone(),
);
instruction_override(&mut set_realm_config_ix);
@ -660,8 +795,31 @@ impl GovernanceProgramTest {
realm_cookie
.account
.config
.community_mint_max_vote_weight_source =
config_args.community_mint_max_vote_weight_source.clone();
.community_mint_max_vote_weight_source = realm_config_args
.community_mint_max_vote_weight_source
.clone();
if realm_config_args.use_community_voter_weight_addin {
let community_voter_weight_addin_index = if realm_config_args.use_council_mint {
7
} else {
5
};
realm_cookie.realm_config = Some(RealmConfigCookie {
address: get_realm_config_address(&self.program_id, &realm_cookie.address),
account: RealmConfigAccount {
account_type: GovernanceAccountType::RealmConfig,
realm: realm_cookie.address,
community_voter_weight_addin: Some(
set_realm_config_ix.accounts[community_voter_weight_addin_index].pubkey,
),
reserved_1: None,
reserved_2: None,
reserved_3: None,
reserved: [0; 128],
},
})
}
self.bench
.process_transaction(&[set_realm_config_ix], Some(signers))
@ -820,12 +978,21 @@ impl GovernanceProgramTest {
token_owner_record_cookie: &TokenOwnerRecordCookie,
governance_config: &GovernanceConfig,
) -> Result<GovernanceCookie, ProgramError> {
let voter_weight_record =
if let Some(voter_weight_record) = &token_owner_record_cookie.voter_weight_record {
Some(voter_weight_record.address)
} else {
None
};
let create_account_governance_instruction = create_account_governance(
&self.program_id,
&realm_cookie.address,
&governed_account_cookie.address,
&token_owner_record_cookie.address,
&self.bench.payer.pubkey(),
&token_owner_record_cookie.token_owner.pubkey(),
voter_weight_record,
governance_config.clone(),
);
@ -839,7 +1006,10 @@ impl GovernanceProgramTest {
};
self.bench
.process_transaction(&[create_account_governance_instruction], None)
.process_transaction(
&[create_account_governance_instruction],
Some(&[&token_owner_record_cookie.token_owner]),
)
.await?;
let account_governance_address = get_account_governance_address(
@ -957,6 +1127,13 @@ impl GovernanceProgramTest {
) -> Result<GovernanceCookie, ProgramError> {
let config = self.get_default_governance_config();
let voter_weight_record =
if let Some(voter_weight_record) = &token_owner_record_cookie.voter_weight_record {
Some(voter_weight_record.address)
} else {
None
};
let mut create_program_governance_instruction = create_program_governance(
&self.program_id,
&realm_cookie.address,
@ -964,13 +1141,18 @@ impl GovernanceProgramTest {
&governed_program_cookie.upgrade_authority.pubkey(),
&token_owner_record_cookie.address,
&self.bench.payer.pubkey(),
&token_owner_record_cookie.token_owner.pubkey(),
voter_weight_record,
config.clone(),
governed_program_cookie.transfer_upgrade_authority,
);
instruction_override(&mut create_program_governance_instruction);
let default_signers = &[&governed_program_cookie.upgrade_authority];
let default_signers = &[
&governed_program_cookie.upgrade_authority,
&token_owner_record_cookie.token_owner,
];
let signers = signers_override.unwrap_or(default_signers);
self.bench
@ -1027,6 +1209,13 @@ impl GovernanceProgramTest {
) -> Result<GovernanceCookie, ProgramError> {
let config = self.get_default_governance_config();
let voter_weight_record =
if let Some(voter_weight_record) = &token_owner_record_cookie.voter_weight_record {
Some(voter_weight_record.address)
} else {
None
};
let mut create_mint_governance_instruction = create_mint_governance(
&self.program_id,
&realm_cookie.address,
@ -1034,13 +1223,18 @@ impl GovernanceProgramTest {
&governed_mint_cookie.mint_authority.pubkey(),
&token_owner_record_cookie.address,
&self.bench.payer.pubkey(),
&token_owner_record_cookie.token_owner.pubkey(),
voter_weight_record,
config.clone(),
governed_mint_cookie.transfer_mint_authority,
);
instruction_override(&mut create_mint_governance_instruction);
let default_signers = &[&governed_mint_cookie.mint_authority];
let default_signers = &[
&governed_mint_cookie.mint_authority,
&token_owner_record_cookie.token_owner,
];
let signers = signers_override.unwrap_or(default_signers);
self.bench
@ -1097,6 +1291,13 @@ impl GovernanceProgramTest {
) -> Result<GovernanceCookie, ProgramError> {
let config = self.get_default_governance_config();
let voter_weight_record =
if let Some(voter_weight_record) = &token_owner_record_cookie.voter_weight_record {
Some(voter_weight_record.address)
} else {
None
};
let mut create_token_governance_instruction = create_token_governance(
&self.program_id,
&realm_cookie.address,
@ -1104,13 +1305,18 @@ impl GovernanceProgramTest {
&governed_token_cookie.token_owner.pubkey(),
&token_owner_record_cookie.address,
&self.bench.payer.pubkey(),
&token_owner_record_cookie.token_owner.pubkey(),
voter_weight_record,
config.clone(),
governed_token_cookie.transfer_token_owner,
);
instruction_override(&mut create_token_governance_instruction);
let default_signers = &[&governed_token_cookie.token_owner];
let default_signers = &[
&governed_token_cookie.token_owner,
&token_owner_record_cookie.token_owner,
];
let signers = signers_override.unwrap_or(default_signers);
self.bench
@ -1189,12 +1395,20 @@ impl GovernanceProgramTest {
let governance_authority = token_owner_record_cookie.get_governance_authority();
let voter_weight_record =
if let Some(voter_weight_record) = &token_owner_record_cookie.voter_weight_record {
Some(voter_weight_record.address)
} else {
None
};
let mut create_proposal_instruction = create_proposal(
&self.program_id,
&governance_cookie.address,
&token_owner_record_cookie.address,
&governance_authority.pubkey(),
&self.bench.payer.pubkey(),
voter_weight_record,
&governance_cookie.account.realm,
name.clone(),
description_link.clone(),
@ -1465,6 +1679,13 @@ impl GovernanceProgramTest {
token_owner_record_cookie: &TokenOwnerRecordCookie,
vote: Vote,
) -> Result<VoteRecordCookie, ProgramError> {
let voter_weight_record =
if let Some(voter_weight_record) = &token_owner_record_cookie.voter_weight_record {
Some(voter_weight_record.address)
} else {
None
};
let vote_instruction = cast_vote(
&self.program_id,
&token_owner_record_cookie.account.realm,
@ -1475,6 +1696,7 @@ impl GovernanceProgramTest {
&token_owner_record_cookie.token_owner.pubkey(),
&proposal_cookie.account.governing_token_mint,
&self.bench.payer.pubkey(),
voter_weight_record,
vote.clone(),
);
@ -1847,9 +2069,17 @@ impl GovernanceProgramTest {
}
#[allow(dead_code)]
pub async fn get_realm_account(&mut self, root_governance_address: &Pubkey) -> Realm {
pub async fn get_realm_account(&mut self, realm_address: &Pubkey) -> Realm {
self.bench.get_borsh_account::<Realm>(realm_address).await
}
#[allow(dead_code)]
pub async fn get_realm_config_data(
&mut self,
realm_config_address: &Pubkey,
) -> RealmConfigAccount {
self.bench
.get_borsh_account::<Realm>(root_governance_address)
.get_borsh_account::<RealmConfigAccount>(realm_config_address)
.await
}
@ -1951,4 +2181,56 @@ impl GovernanceProgramTest {
pub async fn get_mint_account(&mut self, address: &Pubkey) -> spl_token::state::Mint {
self.get_packed_account(address).await
}
/// ----------- VoterWeight Addin -----------------------------
#[allow(dead_code)]
pub async fn with_voter_weight_addin_deposit(
&mut self,
token_owner_record_cookie: &mut TokenOwnerRecordCookie,
) -> Result<VoterWeightRecordCookie, ProgramError> {
let voter_weight_record_account = Keypair::new();
// Governance program has no dependency on the voter-weight-addin program and hence we can't use its instruction creator here
// and the instruction has to be created manually
// TODO: Currently the addin spl_governance_voter_weight_addin.so must be manually copied to tests/fixtures to work on CI
// We should automate this step as part of the build to build the addin before governance
let accounts = vec![
AccountMeta::new_readonly(self.program_id, false),
AccountMeta::new_readonly(token_owner_record_cookie.account.realm, false),
AccountMeta::new_readonly(
token_owner_record_cookie.account.governing_token_mint,
false,
),
AccountMeta::new_readonly(token_owner_record_cookie.address, false),
AccountMeta::new(voter_weight_record_account.pubkey(), true),
AccountMeta::new_readonly(self.bench.payer.pubkey(), true),
AccountMeta::new_readonly(system_program::id(), false),
];
let deposit_ix = Instruction {
program_id: self.voter_weight_addin_id.unwrap(),
accounts,
data: vec![1, 100, 0, 0, 0, 0, 0, 0, 0], // 1 - Deposit instruction, 100 amount (u64)
};
self.bench
.process_transaction(&[deposit_ix], Some(&[&voter_weight_record_account]))
.await?;
let voter_weight_record_cookie = VoterWeightRecordCookie {
address: voter_weight_record_account.pubkey(),
account: VoterWeightRecord {
account_type: VoterWeightAccountType::VoterWeightRecord,
realm: token_owner_record_cookie.account.realm,
governing_token_mint: token_owner_record_cookie.account.governing_token_mint,
governing_token_owner: token_owner_record_cookie.account.governing_token_owner,
voter_weight: 100,
voter_weight_expiry: None,
},
};
token_owner_record_cookie.voter_weight_record = Some(voter_weight_record_cookie.clone());
Ok(voter_weight_record_cookie)
}
}

View File

@ -0,0 +1,217 @@
#![cfg(feature = "test-bpf")]
use solana_program::pubkey::Pubkey;
use solana_program_test::*;
mod program_test;
use program_test::*;
#[tokio::test]
async fn test_create_realm_with_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
// Act
let realm_cookie = governance_test.with_realm().await;
// Assert
let realm_account_data = governance_test
.get_realm_account(&realm_cookie.address)
.await;
assert!(realm_account_data.config.use_community_voter_weight_addin);
let realm_config_cookie = realm_cookie.realm_config.unwrap();
let realm_config_data = governance_test
.get_realm_config_data(&realm_config_cookie.address)
.await;
assert_eq!(realm_config_cookie.account, realm_config_data);
}
#[tokio::test]
async fn test_set_realm_voter_weight_addin_for_realm_without_addins() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let mut realm_config_args = governance_test.get_default_realm_config_args();
realm_config_args.use_community_voter_weight_addin = false;
let mut realm_cookie = governance_test
.with_realm_using_config_args(&realm_config_args)
.await;
realm_config_args.use_community_voter_weight_addin = true;
// Act
governance_test
.set_realm_config(&mut realm_cookie, &realm_config_args)
.await
.unwrap();
// Assert
let realm_account_data = governance_test
.get_realm_account(&realm_cookie.address)
.await;
assert!(realm_account_data.config.use_community_voter_weight_addin);
let realm_config_cookie = realm_cookie.realm_config.unwrap();
let realm_config_data = governance_test
.get_realm_config_data(&realm_config_cookie.address)
.await;
assert_eq!(realm_config_cookie.account, realm_config_data);
}
#[tokio::test]
async fn test_set_realm_voter_weight_addin_for_realm_without_council_and_addins() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let mut realm_config_args = governance_test.get_default_realm_config_args();
realm_config_args.use_community_voter_weight_addin = false;
realm_config_args.use_council_mint = false;
let mut realm_cookie = governance_test
.with_realm_using_config_args(&realm_config_args)
.await;
realm_config_args.use_community_voter_weight_addin = true;
// Act
governance_test
.set_realm_config(&mut realm_cookie, &realm_config_args)
.await
.unwrap();
// Assert
let realm_account_data = governance_test
.get_realm_account(&realm_cookie.address)
.await;
assert!(realm_account_data.config.use_community_voter_weight_addin);
let realm_config_cookie = realm_cookie.realm_config.unwrap();
let realm_config_data = governance_test
.get_realm_config_data(&realm_config_cookie.address)
.await;
assert_eq!(realm_config_cookie.account, realm_config_data);
}
#[tokio::test]
async fn test_set_realm_voter_weight_addin_for_realm_with_existing_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let mut realm_cookie = governance_test.with_realm().await;
let mut realm_config_args = governance_test.get_default_realm_config_args();
realm_config_args.use_community_voter_weight_addin = true;
let community_voter_weight_addin_address = Pubkey::new_unique();
// Act
governance_test
.set_realm_config_using_instruction(
&mut realm_cookie,
&realm_config_args,
|i| i.accounts[7].pubkey = community_voter_weight_addin_address,
None,
)
.await
.unwrap();
// Assert
let realm_account_data = governance_test
.get_realm_account(&realm_cookie.address)
.await;
assert!(realm_account_data.config.use_community_voter_weight_addin);
let realm_config_cookie = realm_cookie.realm_config.unwrap();
let realm_config_data = governance_test
.get_realm_config_data(&realm_config_cookie.address)
.await;
assert_eq!(realm_config_cookie.account, realm_config_data);
assert_eq!(
realm_config_data.community_voter_weight_addin,
Some(community_voter_weight_addin_address)
);
}
#[tokio::test]
async fn test_set_realm_config_with_no_voter_weight_addin_for_realm_without_addins() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let mut realm_config_args = governance_test.get_default_realm_config_args();
realm_config_args.use_community_voter_weight_addin = false;
let mut realm_cookie = governance_test
.with_realm_using_config_args(&realm_config_args)
.await;
realm_config_args.use_community_voter_weight_addin = false;
// Act
governance_test
.set_realm_config(&mut realm_cookie, &realm_config_args)
.await
.unwrap();
// Assert
let realm_account_data = governance_test
.get_realm_account(&realm_cookie.address)
.await;
assert!(!realm_account_data.config.use_community_voter_weight_addin);
}
#[tokio::test]
async fn test_set_realm_config_with_no_voter_weight_addin_for_realm_with_existing_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let mut realm_cookie = governance_test.with_realm().await;
let mut realm_config_args = governance_test.get_default_realm_config_args();
realm_config_args.use_community_voter_weight_addin = false;
// Act
governance_test
.set_realm_config(&mut realm_cookie, &realm_config_args)
.await
.unwrap();
// Assert
let realm_account_data = governance_test
.get_realm_account(&realm_cookie.address)
.await;
assert!(!realm_account_data.config.use_community_voter_weight_addin);
let realm_config_data = governance_test
.get_realm_config_data(&realm_cookie.realm_config.unwrap().address)
.await;
assert!(realm_config_data.community_voter_weight_addin.is_none());
}

View File

@ -0,0 +1,261 @@
#![cfg(feature = "test-bpf")]
use solana_program_test::*;
mod program_test;
use program_test::*;
use spl_governance::{error::GovernanceError, instruction::Vote, state::enums::VoteWeight};
#[tokio::test]
async fn test_create_account_governance_with_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
governance_test
.with_voter_weight_addin_deposit(&mut token_owner_record_cookie)
.await
.unwrap();
// Act
let account_governance_cookie = governance_test
.with_account_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
// // Assert
let account_governance_account = governance_test
.get_governance_account(&account_governance_cookie.address)
.await;
assert_eq!(
account_governance_cookie.account,
account_governance_account
);
}
#[tokio::test]
async fn test_create_proposal_with_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
governance_test
.with_voter_weight_addin_deposit(&mut token_owner_record_cookie)
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
// Act
let proposal_cookie = governance_test
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
.await
.unwrap();
// // Assert
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(proposal_cookie.account, proposal_account);
}
#[tokio::test]
async fn test_cast_vote_with_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let governed_account_cookie = governance_test.with_governed_account().await;
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
governance_test
.with_voter_weight_addin_deposit(&mut token_owner_record_cookie)
.await
.unwrap();
let mut account_governance_cookie = governance_test
.with_account_governance(
&realm_cookie,
&governed_account_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
let proposal_cookie = governance_test
.with_signed_off_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
.await
.unwrap();
// Act
let vote_record_cookie = governance_test
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, Vote::Yes)
.await
.unwrap();
// Assert
let vote_record_account = governance_test
.get_vote_record_account(&vote_record_cookie.address)
.await;
assert_eq!(VoteWeight::Yes(100), vote_record_account.vote_weight);
let proposal_account = governance_test
.get_proposal_account(&proposal_cookie.address)
.await;
assert_eq!(100, proposal_account.yes_votes_count);
}
#[tokio::test]
async fn test_create_token_governance_with_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let governed_token_cookie = governance_test.with_governed_token().await;
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
governance_test
.with_voter_weight_addin_deposit(&mut token_owner_record_cookie)
.await
.unwrap();
// Act
let token_governance_cookie = governance_test
.with_token_governance(
&realm_cookie,
&governed_token_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
// // Assert
let token_governance_account = governance_test
.get_governance_account(&token_governance_cookie.address)
.await;
assert_eq!(token_governance_cookie.account, token_governance_account);
}
#[tokio::test]
async fn test_create_mint_governance_with_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let governed_mint_cookie = governance_test.with_governed_mint().await;
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
governance_test
.with_voter_weight_addin_deposit(&mut token_owner_record_cookie)
.await
.unwrap();
// Act
let mint_governance_cookie = governance_test
.with_mint_governance(
&realm_cookie,
&governed_mint_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
// // Assert
let mint_governance_account = governance_test
.get_governance_account(&mint_governance_cookie.address)
.await;
assert_eq!(mint_governance_cookie.account, mint_governance_account);
}
#[tokio::test]
async fn test_create_program_governance_with_voter_weight_addin() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let governed_program_cookie = governance_test.with_governed_program().await;
let realm_cookie = governance_test.with_realm().await;
let mut token_owner_record_cookie =
governance_test.with_token_owner_record(&realm_cookie).await;
governance_test
.with_voter_weight_addin_deposit(&mut token_owner_record_cookie)
.await
.unwrap();
// Act
let program_governance_cookie = governance_test
.with_program_governance(
&realm_cookie,
&governed_program_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
// Assert
let program_governance_account = governance_test
.get_governance_account(&program_governance_cookie.address)
.await;
assert_eq!(
program_governance_cookie.account,
program_governance_account
);
}
#[tokio::test]
async fn test_realm_with_voter_weight_addin_with_deposits_not_allowed() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_with_voter_weight_addin().await;
let realm_cookie = governance_test.with_realm().await;
// Act
let err = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.err()
.unwrap();
// Assert
assert_eq!(
err,
GovernanceError::GoverningTokenDepositsNotAllowed.into()
);
}

View File

@ -0,0 +1,3 @@
# Governance Voter Weight Addin
Governance Voter Weight Addin is a skeleton program which can be used as a template to create custom voter weight addins

View File

@ -0,0 +1,39 @@
[package]
name = "spl-governance-voter-weight-addin"
version = "0.1.0"
description = "Solana Program Library Governance Voter Weight Addin Program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2018"
[features]
no-entrypoint = []
test-bpf = []
[dependencies]
arrayref = "0.3.6"
bincode = "1.3.2"
borsh = "0.9.1"
num-derive = "0.3"
num-traits = "0.2"
serde = "1.0.127"
serde_derive = "1.0.103"
solana-program = "1.7.11"
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] }
spl-governance= { version = "2.1.1", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance-chat= { version = "0.1.0", path ="../../chat/program", features = [ "no-entrypoint" ]}
thiserror = "1.0"
[dev-dependencies]
assert_matches = "1.5.0"
base64 = "0.13"
proptest = "1.0"
solana-program-test = "1.7.11"
solana-sdk = "1.7.11"
spl-governance-test-sdk = { version = "0.1.0", path ="../../test-sdk"}
[lib]
crate-type = ["cdylib", "lib"]

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,22 @@
//! Program entrypoint
#![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))]
use crate::{error::VoterWeightAddinError, processor};
use solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult,
program_error::PrintProgramError, pubkey::Pubkey,
};
entrypoint!(process_instruction);
fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
if let Err(error) = processor::process_instruction(program_id, accounts, instruction_data) {
// catch the error so we can print it
error.print::<VoterWeightAddinError>();
return Err(error);
}
Ok(())
}

View File

@ -0,0 +1,31 @@
//! Error types
use num_derive::FromPrimitive;
use solana_program::{
decode_error::DecodeError,
msg,
program_error::{PrintProgramError, ProgramError},
};
use thiserror::Error;
/// Errors that may be returned by the VoterWeightAddin program
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum VoterWeightAddinError {}
impl PrintProgramError for VoterWeightAddinError {
fn print<E>(&self) {
msg!("VOTER-WEIGHT-ADDIN-ERROR: {}", &self.to_string());
}
}
impl From<VoterWeightAddinError> for ProgramError {
fn from(e: VoterWeightAddinError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl<T> DecodeError<T> for VoterWeightAddinError {
fn type_of() -> &'static str {
"Voter Weight Addin Error"
}
}

View File

@ -0,0 +1,42 @@
//! Program instructions
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
/// Instructions supported by the VoterWeightInstruction addin program
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
#[allow(clippy::large_enum_variant)]
pub enum VoterWeightAddinInstruction {
/// Revises voter weight providing up to date voter weight
///
/// 0. `[]` Governance Program Id
/// 1. `[]` Realm account
/// 2. `[]` Governing Token mint
/// 3. `[]` TokenOwnerRecord
/// 4. `[writable]` VoterWeightRecord
Revise {},
/// Deposits governing token
/// 0. `[]` Governance Program Id
/// 1. `[]` Realm account
/// 2. `[]` Governing Token mint
/// 3. `[]` TokenOwnerRecord
/// 4. `[writable]` VoterWeightRecord
/// 5. `[signer]` Payer
/// 6. `[]` System
Deposit {
/// The deposit amount
#[allow(dead_code)]
amount: u64,
},
/// Withdraws deposited tokens
/// Note: This instruction should ensure the tokens can be withdrawn form the Realm
/// by calling TokenOwnerRecord.assert_can_withdraw_governing_tokens()
///
/// 0. `[]` Governance Program Id
/// 1. `[]` Realm account
/// 2. `[]` Governing Token mint
/// 3. `[]` TokenOwnerRecord
/// 4. `[writable]` VoterWeightRecord
Withdraw {},
}

View File

@ -0,0 +1,12 @@
#![deny(missing_docs)]
//! Governance VoterWeight Addin program
pub mod entrypoint;
pub mod error;
pub mod instruction;
pub mod processor;
//pub mod state;
// pub mod tools;
// Export current sdk types for downstream users building with a different sdk version
pub use solana_program;

View File

@ -0,0 +1,84 @@
//! Program processor
use borsh::BorshDeserialize;
use spl_governance::{
addins::voter_weight::{VoterWeightAccountType, VoterWeightRecord},
state::token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
};
// TODO: Move to shared governance tools
use spl_governance_chat::tools::account::create_and_serialize_account;
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
msg,
program_error::ProgramError,
pubkey::Pubkey,
};
use crate::instruction::VoterWeightAddinInstruction;
/// Processes an instruction
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
input: &[u8],
) -> ProgramResult {
let instruction = VoterWeightAddinInstruction::try_from_slice(input)
.map_err(|_| ProgramError::InvalidInstructionData)?;
msg!("GOVERNANCE-VOTER-WEIGHT-INSTRUCTION: {:?}", instruction);
match instruction {
VoterWeightAddinInstruction::Revise {} => Ok(()),
VoterWeightAddinInstruction::Deposit { amount } => {
process_deposit(program_id, accounts, amount)
}
VoterWeightAddinInstruction::Withdraw {} => Ok(()),
}
}
/// Processes Deposit instruction
pub fn process_deposit(
program_id: &Pubkey,
accounts: &[AccountInfo],
amount: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let governance_program_info = next_account_info(account_info_iter)?; // 0
let realm_info = next_account_info(account_info_iter)?; // 1
let governing_token_mint_info = next_account_info(account_info_iter)?; // 2
let token_owner_record_info = next_account_info(account_info_iter)?; // 3
let voter_weight_record_info = next_account_info(account_info_iter)?; // 4
let payer_info = next_account_info(account_info_iter)?; // 5
let system_info = next_account_info(account_info_iter)?; // 6
let token_owner_record_data = get_token_owner_record_data_for_realm_and_governing_mint(
governance_program_info.key,
token_owner_record_info,
realm_info.key,
governing_token_mint_info.key,
)?;
// TODO: Custom deposit logic and validation goes here
let voter_weight_record_data = VoterWeightRecord {
account_type: VoterWeightAccountType::VoterWeightRecord,
realm: *realm_info.key,
governing_token_mint: *governing_token_mint_info.key,
governing_token_owner: token_owner_record_data.governing_token_owner,
voter_weight: amount,
voter_weight_expiry: None,
};
create_and_serialize_account(
payer_info,
voter_weight_record_info,
&voter_weight_record_data,
program_id,
system_info,
)?;
Ok(())
}