diff --git a/Cargo.lock b/Cargo.lock index 6ed5ad421a1..263f1f209bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 919dd29e345..ad852ae1198 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/governance/chat/program/Cargo.toml b/governance/chat/program/Cargo.toml index f311306fad2..705dcb7dd57 100644 --- a/governance/chat/program/Cargo.toml +++ b/governance/chat/program/Cargo.toml @@ -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" diff --git a/governance/chat/program/tests/program_test/mod.rs b/governance/chat/program/tests/program_test/mod.rs index 4808b299d8f..1d656df79d2 100644 --- a/governance/chat/program/tests/program_test/mod.rs +++ b/governance/chat/program/tests/program_test/mod.rs @@ -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(), diff --git a/governance/program/Cargo.toml b/governance/program/Cargo.toml index 6f5c3620a9a..1fc8c17bb85 100644 --- a/governance/program/Cargo.toml +++ b/governance/program/Cargo.toml @@ -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 "] 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"] diff --git a/governance/program/src/addins/mod.rs b/governance/program/src/addins/mod.rs new file mode 100644 index 00000000000..a485c577583 --- /dev/null +++ b/governance/program/src/addins/mod.rs @@ -0,0 +1,2 @@ +//! Governance add-ins interfaces +pub mod voter_weight; diff --git a/governance/program/src/addins/voter_weight.rs b/governance/program/src/addins/voter_weight.rs new file mode 100644 index 00000000000..2ef864b37ee --- /dev/null +++ b/governance/program/src/addins/voter_weight.rs @@ -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, +} + +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 { + get_account_data::(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 { + 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) +} diff --git a/governance/program/src/error.rs b/governance/program/src/error.rs index 7c9d9a40a15..75b04b1654f 100644 --- a/governance/program/src/error.rs +++ b/governance/program/src/error.rs @@ -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 { diff --git a/governance/program/src/instruction.rs b/governance/program/src/instruction.rs index dcc6f3c15d5..d6bd22febec 100644 --- a/governance/program/src/instruction.rs +++ b/governance/program/src/instruction.rs @@ -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, + community_voter_weight_addin: Option, // 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, // 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, // 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, // 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, // 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, // 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, // 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, - + payer: &Pubkey, + community_voter_weight_addin: Option, // 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, + realm: &Pubkey, + voter_weight_record: Option, +) { + 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(), + } +} diff --git a/governance/program/src/lib.rs b/governance/program/src/lib.rs index f69bc607ed2..47e4a7e56c5 100644 --- a/governance/program/src/lib.rs +++ b/governance/program/src/lib.rs @@ -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; diff --git a/governance/program/src/processor/mod.rs b/governance/program/src/processor/mod.rs index d913088f3b0..67406e89092 100644 --- a/governance/program/src/processor/mod.rs +++ b/governance/program/src/processor/mod.rs @@ -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) + } } } diff --git a/governance/program/src/processor/process_cast_vote.rs b/governance/program/src/processor/process_cast_vote.rs index 36eb81af3cd..13a130f450c 100644 --- a/governance/program/src/processor/process_cast_vote.rs +++ b/governance/program/src/processor/process_cast_vote.rs @@ -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) } }; diff --git a/governance/program/src/processor/process_create_account_governance.rs b/governance/program/src/processor/process_create_account_governance.rs index 19c0001e237..52e1560723e 100644 --- a/governance/program/src/processor/process_create_account_governance.rs +++ b/governance/program/src/processor/process_create_account_governance.rs @@ -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, diff --git a/governance/program/src/processor/process_create_mint_governance.rs b/governance/program/src/processor/process_create_mint_governance.rs index 050b8733f79..9ecd2b34049 100644 --- a/governance/program/src/processor/process_create_mint_governance.rs +++ b/governance/program/src/processor/process_create_mint_governance.rs @@ -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, diff --git a/governance/program/src/processor/process_create_program_governance.rs b/governance/program/src/processor/process_create_program_governance.rs index 38d49c2e181..b7359ed70e6 100644 --- a/governance/program/src/processor/process_create_program_governance.rs +++ b/governance/program/src/processor/process_create_program_governance.rs @@ -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, diff --git a/governance/program/src/processor/process_create_proposal.rs b/governance/program/src/processor/process_create_proposal.rs index 9a52af5828c..4a094a6409d 100644 --- a/governance/program/src/processor/process_create_proposal.rs +++ b/governance/program/src/processor/process_create_proposal.rs @@ -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 diff --git a/governance/program/src/processor/process_create_realm.rs b/governance/program/src/processor/process_create_realm.rs index cfdca86ba0d..0e425efdfea 100644 --- a/governance/program/src/processor/process_create_realm.rs +++ b/governance/program/src/processor/process_create_realm.rs @@ -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::( + 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, }, }; diff --git a/governance/program/src/processor/process_create_token_governance.rs b/governance/program/src/processor/process_create_token_governance.rs index 505299746c9..a1dde948154 100644 --- a/governance/program/src/processor/process_create_token_governance.rs +++ b/governance/program/src/processor/process_create_token_governance.rs @@ -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, diff --git a/governance/program/src/processor/process_create_token_owner_record.rs b/governance/program/src/processor/process_create_token_owner_record.rs new file mode 100644 index 00000000000..d643592fdc0 --- /dev/null +++ b/governance/program/src/processor/process_create_token_owner_record.rs @@ -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, + ) +} diff --git a/governance/program/src/processor/process_deposit_governing_tokens.rs b/governance/program/src/processor/process_deposit_governing_tokens.rs index a65c6b9df03..08d434f25fd 100644 --- a/governance/program/src/processor/process_deposit_governing_tokens.rs +++ b/governance/program/src/processor/process_deposit_governing_tokens.rs @@ -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, diff --git a/governance/program/src/processor/process_set_realm_config.rs b/governance/program/src/processor/process_set_realm_config.rs index 233971f6227..a9093a76d6f 100644 --- a/governance/program/src/processor/process_set_realm_config.rs +++ b/governance/program/src/processor/process_set_realm_config.rs @@ -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::( + 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())?; diff --git a/governance/program/src/processor/process_withdraw_governing_tokens.rs b/governance/program/src/processor/process_withdraw_governing_tokens.rs index dbdd6d0ef86..6d8b12e62d4 100644 --- a/governance/program/src/processor/process_withdraw_governing_tokens.rs +++ b/governance/program/src/processor/process_withdraw_governing_tokens.rs @@ -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, diff --git a/governance/program/src/state/enums.rs b/governance/program/src/state/enums.rs index 5e4546274f9..5d502fca3fc 100644 --- a/governance/program/src/state/enums.rs +++ b/governance/program/src/state/enums.rs @@ -38,6 +38,9 @@ pub enum GovernanceAccountType { /// Token Governance account TokenGovernance, + + /// Realm config account + RealmConfig, } impl Default for GovernanceAccountType { diff --git a/governance/program/src/state/mod.rs b/governance/program/src/state/mod.rs index 43a953b155d..0fd5812723c 100644 --- a/governance/program/src/state/mod.rs +++ b/governance/program/src/state/mod.rs @@ -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; diff --git a/governance/program/src/state/proposal.rs b/governance/program/src/state/proposal.rs index 64b5353e8a3..d48c8673ff3 100644 --- a/governance/program/src/state/proposal.rs +++ b/governance/program/src/state/proposal.rs @@ -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, diff --git a/governance/program/src/state/realm.rs b/governance/program/src/state/realm.rs index d301add5f21..6e04188df72 100644 --- a/governance/program/src/state/realm.rs +++ b/governance/program/src/state/realm.rs @@ -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"); + } + } } diff --git a/governance/program/src/state/realm_config.rs b/governance/program/src/state/realm_config.rs new file mode 100644 index 00000000000..4b131d77579 --- /dev/null +++ b/governance/program/src/state/realm_config.rs @@ -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, + + /// Reserved for community max vote weight addin + pub reserved_1: Option, + + /// Reserved for council voter weight addin + pub reserved_2: Option, + + /// Reserved for council max vote weight addin + pub reserved_3: Option, + + /// Reserved + pub reserved: [u8; 128], +} + +impl AccountMaxSize for RealmConfigAccount { + fn get_max_size(&self) -> Option { + 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 { + get_account_data::(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 { + 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)); + } +} diff --git a/governance/program/src/state/token_owner_record.rs b/governance/program/src/state/token_owner_record.rs index 5d2fd0d1d43..3aeed227729 100644 --- a/governance/program/src/state/token_owner_record.rs +++ b/governance/program/src/state/token_owner_record.rs @@ -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, + realm: &Pubkey, + realm_data: &Realm, + ) -> Result { + // 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 diff --git a/governance/program/tests/fixtures/spl_governance_voter_weight_addin.so b/governance/program/tests/fixtures/spl_governance_voter_weight_addin.so new file mode 100755 index 00000000000..ef99326389d Binary files /dev/null and b/governance/program/tests/fixtures/spl_governance_voter_weight_addin.so differ diff --git a/governance/program/tests/process_add_signatory.rs b/governance/program/tests/process_add_signatory.rs index 2f78c170a67..cfa24f554f2 100644 --- a/governance/program/tests/process_add_signatory.rs +++ b/governance/program/tests/process_add_signatory.rs @@ -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; diff --git a/governance/program/tests/process_cancel_proposal.rs b/governance/program/tests/process_cancel_proposal.rs index c449fb85ac1..d28fadda8a8 100644 --- a/governance/program/tests/process_cancel_proposal.rs +++ b/governance/program/tests/process_cancel_proposal.rs @@ -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; diff --git a/governance/program/tests/process_cast_vote.rs b/governance/program/tests/process_cast_vote.rs index f94593979b4..250236bdd17 100644 --- a/governance/program/tests/process_cast_vote.rs +++ b/governance/program/tests/process_cast_vote.rs @@ -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( diff --git a/governance/program/tests/process_create_account_governance.rs b/governance/program/tests/process_create_account_governance.rs index f84974cd1a8..827c3ce770f 100644 --- a/governance/program/tests/process_create_account_governance.rs +++ b/governance/program/tests/process_create_account_governance.rs @@ -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 diff --git a/governance/program/tests/process_create_mint_governance.rs b/governance/program/tests/process_create_mint_governance.rs index bc6efeec93c..279fcb80040 100644 --- a/governance/program/tests/process_create_mint_governance.rs +++ b/governance/program/tests/process_create_mint_governance.rs @@ -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( diff --git a/governance/program/tests/process_create_program_governance.rs b/governance/program/tests/process_create_program_governance.rs index e856a168577..3b9782b27c8 100644 --- a/governance/program/tests/process_create_program_governance.rs +++ b/governance/program/tests/process_create_program_governance.rs @@ -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( diff --git a/governance/program/tests/process_create_proposal.rs b/governance/program/tests/process_create_proposal.rs index a6457504fa7..3a5864cf34f 100644 --- a/governance/program/tests/process_create_proposal.rs +++ b/governance/program/tests/process_create_proposal.rs @@ -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; diff --git a/governance/program/tests/process_create_realm.rs b/governance/program/tests/process_create_realm.rs index 7e4a3f6e59c..bca8d63f0e7 100644 --- a/governance/program/tests/process_create_realm.rs +++ b/governance/program/tests/process_create_realm.rs @@ -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 diff --git a/governance/program/tests/process_create_token_governance.rs b/governance/program/tests/process_create_token_governance.rs index c10bcd5fb66..eaac98dcfca 100644 --- a/governance/program/tests/process_create_token_governance.rs +++ b/governance/program/tests/process_create_token_governance.rs @@ -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( diff --git a/governance/program/tests/process_create_token_owner_record.rs b/governance/program/tests/process_create_token_owner_record.rs new file mode 100644 index 00000000000..2172cd156bd --- /dev/null +++ b/governance/program/tests/process_create_token_owner_record.rs @@ -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 + ); +} diff --git a/governance/program/tests/process_deposit_governing_tokens.rs b/governance/program/tests/process_deposit_governing_tokens.rs index 0891de14d41..678136bda62 100644 --- a/governance/program/tests/process_deposit_governing_tokens.rs +++ b/governance/program/tests/process_deposit_governing_tokens.rs @@ -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 diff --git a/governance/program/tests/process_execute_instruction.rs b/governance/program/tests/process_execute_instruction.rs index d4c89590016..717b1996074 100644 --- a/governance/program/tests/process_execute_instruction.rs +++ b/governance/program/tests/process_execute_instruction.rs @@ -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( diff --git a/governance/program/tests/process_finalize_vote.rs b/governance/program/tests/process_finalize_vote.rs index 0f9d64e9810..571568b4877 100644 --- a/governance/program/tests/process_finalize_vote.rs +++ b/governance/program/tests/process_finalize_vote.rs @@ -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( diff --git a/governance/program/tests/process_flag_instruction_error.rs b/governance/program/tests/process_flag_instruction_error.rs index b302e9dedda..cfb00cb71d8 100644 --- a/governance/program/tests/process_flag_instruction_error.rs +++ b/governance/program/tests/process_flag_instruction_error.rs @@ -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; diff --git a/governance/program/tests/process_insert_instruction.rs b/governance/program/tests/process_insert_instruction.rs index 4dd8746f49b..33f56fa8300 100644 --- a/governance/program/tests/process_insert_instruction.rs +++ b/governance/program/tests/process_insert_instruction.rs @@ -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( diff --git a/governance/program/tests/process_relinquish_vote.rs b/governance/program/tests/process_relinquish_vote.rs index bf3b7892df8..b9fe6555ba5 100644 --- a/governance/program/tests/process_relinquish_vote.rs +++ b/governance/program/tests/process_relinquish_vote.rs @@ -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( diff --git a/governance/program/tests/process_remove_instruction.rs b/governance/program/tests/process_remove_instruction.rs index 0a6b7bd760e..580ecc6a79f 100644 --- a/governance/program/tests/process_remove_instruction.rs +++ b/governance/program/tests/process_remove_instruction.rs @@ -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) diff --git a/governance/program/tests/process_remove_signatory.rs b/governance/program/tests/process_remove_signatory.rs index 7bf2f4d69e1..02a27042cc6 100644 --- a/governance/program/tests/process_remove_signatory.rs +++ b/governance/program/tests/process_remove_signatory.rs @@ -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( diff --git a/governance/program/tests/process_set_governance_config.rs b/governance/program/tests/process_set_governance_config.rs index b867dc8272e..95a5b04f027 100644 --- a/governance/program/tests/process_set_governance_config.rs +++ b/governance/program/tests/process_set_governance_config.rs @@ -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( diff --git a/governance/program/tests/process_set_governance_delegate.rs b/governance/program/tests/process_set_governance_delegate.rs index 08e0cd702f4..588844522b8 100644 --- a/governance/program/tests/process_set_governance_delegate.rs +++ b/governance/program/tests/process_set_governance_delegate.rs @@ -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) diff --git a/governance/program/tests/process_set_realm_config.rs b/governance/program/tests/process_set_realm_config.rs index a05e5ce07dd..22dd7fadacf 100644 --- a/governance/program/tests/process_set_realm_config.rs +++ b/governance/program/tests/process_set_realm_config.rs @@ -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 diff --git a/governance/program/tests/process_sign_off_proposal.rs b/governance/program/tests/process_sign_off_proposal.rs index 006738895fd..7d2b3c9ca52 100644 --- a/governance/program/tests/process_sign_off_proposal.rs +++ b/governance/program/tests/process_sign_off_proposal.rs @@ -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( diff --git a/governance/program/tests/process_withdraw_governing_tokens.rs b/governance/program/tests/process_withdraw_governing_tokens.rs index 0e4ae38bdb0..7ceee5b0c2d 100644 --- a/governance/program/tests/process_withdraw_governing_tokens.rs +++ b/governance/program/tests/process_withdraw_governing_tokens.rs @@ -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, diff --git a/governance/program/tests/program_test/cookies.rs b/governance/program/tests/program_test/cookies.rs index 45939e281c1..bf572a88699 100644 --- a/governance/program/tests/program_test/cookies.rs +++ b/governance/program/tests/program_test/cookies.rs @@ -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, pub realm_authority: Option, + + pub realm_config: Option, +} + +#[derive(Debug)] +pub struct RealmConfigCookie { + pub address: Pubkey, + pub account: RealmConfigAccount, } #[derive(Debug)] @@ -44,6 +55,8 @@ pub struct TokenOwnerRecordCookie { pub governance_authority: Option, pub governance_delegate: Keypair, + + pub voter_weight_record: Option, } 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, +} diff --git a/governance/program/tests/program_test/mod.rs b/governance/program/tests/program_test/mod.rs index 6214dc78903..fb666b3df94 100644 --- a/governance/program/tests/program_test/mod.rs +++ b/governance/program/tests/program_test/mod.rs @@ -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, } 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 async fn with_realm(&mut self) -> RealmCookie { - let config_args = RealmConfigArgs { + 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(), + } + } - self.with_realm_using_config_args(&config_args).await + #[allow(dead_code)] + pub async fn with_realm(&mut self) -> RealmCookie { + 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 { 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 { 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 { 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 { 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 { 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( &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 { + 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 { 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 { 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 { 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 { + 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_address).await + } + + #[allow(dead_code)] + pub async fn get_realm_config_data( + &mut self, + realm_config_address: &Pubkey, + ) -> RealmConfigAccount { self.bench - .get_borsh_account::(root_governance_address) + .get_borsh_account::(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 { + 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) + } } diff --git a/governance/program/tests/setup_realm_with_voter_weight_addin.rs b/governance/program/tests/setup_realm_with_voter_weight_addin.rs new file mode 100644 index 00000000000..9218347e0cc --- /dev/null +++ b/governance/program/tests/setup_realm_with_voter_weight_addin.rs @@ -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()); +} diff --git a/governance/program/tests/use_realm_with_voter_weight_addin.rs b/governance/program/tests/use_realm_with_voter_weight_addin.rs new file mode 100644 index 00000000000..13dc0a1ff97 --- /dev/null +++ b/governance/program/tests/use_realm_with_voter_weight_addin.rs @@ -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() + ); +} diff --git a/governance/voter-weight-addin/README.md b/governance/voter-weight-addin/README.md new file mode 100644 index 00000000000..5cac8570787 --- /dev/null +++ b/governance/voter-weight-addin/README.md @@ -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 diff --git a/governance/voter-weight-addin/program/Cargo.toml b/governance/voter-weight-addin/program/Cargo.toml new file mode 100644 index 00000000000..abc5468551e --- /dev/null +++ b/governance/voter-weight-addin/program/Cargo.toml @@ -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 "] +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"] diff --git a/governance/voter-weight-addin/program/Xargo.toml b/governance/voter-weight-addin/program/Xargo.toml new file mode 100644 index 00000000000..1744f098ae1 --- /dev/null +++ b/governance/voter-weight-addin/program/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/governance/voter-weight-addin/program/src/entrypoint.rs b/governance/voter-weight-addin/program/src/entrypoint.rs new file mode 100644 index 00000000000..cfef2217179 --- /dev/null +++ b/governance/voter-weight-addin/program/src/entrypoint.rs @@ -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::(); + return Err(error); + } + Ok(()) +} diff --git a/governance/voter-weight-addin/program/src/error.rs b/governance/voter-weight-addin/program/src/error.rs new file mode 100644 index 00000000000..10dd08d4e40 --- /dev/null +++ b/governance/voter-weight-addin/program/src/error.rs @@ -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(&self) { + msg!("VOTER-WEIGHT-ADDIN-ERROR: {}", &self.to_string()); + } +} + +impl From for ProgramError { + fn from(e: VoterWeightAddinError) -> Self { + ProgramError::Custom(e as u32) + } +} + +impl DecodeError for VoterWeightAddinError { + fn type_of() -> &'static str { + "Voter Weight Addin Error" + } +} diff --git a/governance/voter-weight-addin/program/src/instruction.rs b/governance/voter-weight-addin/program/src/instruction.rs new file mode 100644 index 00000000000..b94ebe599ba --- /dev/null +++ b/governance/voter-weight-addin/program/src/instruction.rs @@ -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 {}, +} diff --git a/governance/voter-weight-addin/program/src/lib.rs b/governance/voter-weight-addin/program/src/lib.rs new file mode 100644 index 00000000000..53b71f4a339 --- /dev/null +++ b/governance/voter-weight-addin/program/src/lib.rs @@ -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; diff --git a/governance/voter-weight-addin/program/src/processor.rs b/governance/voter-weight-addin/program/src/processor.rs new file mode 100644 index 00000000000..01cacd7ace2 --- /dev/null +++ b/governance/voter-weight-addin/program/src/processor.rs @@ -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(()) +}