From a74c5b998b432f55fd8492e28cdbb17c53daded2 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Thu, 29 Jul 2021 17:04:05 +0100 Subject: [PATCH] Governance: Realm config (#2166) * feat: create RealmConfig and move council to it * feat: add realm custodian to config * feat: add community_mint_max_vote_weight_source tp realm config * chore: fix data types --- Cargo.lock | 2 +- governance/program/Cargo.toml | 2 +- governance/program/src/instruction.rs | 34 +++++++++------ .../src/processor/process_cast_vote.rs | 21 ++++----- .../src/processor/process_create_realm.rs | 14 ++++-- .../src/processor/process_finalize_vote.rs | 9 ++-- governance/program/src/state/enums.rs | 14 ++++++ governance/program/src/state/realm.rs | 43 +++++++++++++++---- .../program/src/state/token_owner_record.rs | 2 +- governance/program/tests/process_cast_vote.rs | 3 +- .../program/tests/process_create_proposal.rs | 2 +- .../program/tests/process_finalize_vote.rs | 8 ++-- governance/program/tests/program_test/mod.rs | 35 ++++++++++----- 13 files changed, 131 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55f62ff2..5219e6a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3758,7 +3758,7 @@ dependencies = [ [[package]] name = "spl-governance" -version = "1.0.4" +version = "1.0.6" dependencies = [ "arrayref", "assert_matches", diff --git a/governance/program/Cargo.toml b/governance/program/Cargo.toml index d9d2627e..a57f1344 100644 --- a/governance/program/Cargo.toml +++ b/governance/program/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "spl-governance" -version = "1.0.4" +version = "1.0.6" description = "Solana Program Library Governance Program" authors = ["Solana Maintainers "] repository = "https://github.com/solana-labs/solana-program-library" diff --git a/governance/program/src/instruction.rs b/governance/program/src/instruction.rs index 01658183..a5ec1791 100644 --- a/governance/program/src/instruction.rs +++ b/governance/program/src/instruction.rs @@ -244,16 +244,17 @@ pub enum GovernanceInstruction { /// By doing so you indicate you approve or disapprove of running the Proposal set of instructions /// If you tip the consensus then the instructions can begin to be run after their hold up time /// - /// 0. `[]` Governance account - /// 1. `[writable]` Proposal account - /// 2. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner] - /// 3. `[signer]` Governance Authority (Token Owner or Governance Delegate) - /// 4. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record] - /// 5. `[]` Governing Token Mint - /// 6. `[signer]` Payer - /// 7. `[]` System program - /// 8. `[]` Rent sysvar - /// 9. `[]` Clock sysvar + /// 0. `[]` Realm account + /// 1. `[]` Governance account + /// 2. `[writable]` Proposal account + /// 3. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner] + /// 4. `[signer]` Governance Authority (Token Owner or Governance Delegate) + /// 5. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record] + /// 6. `[]` Governing Token Mint + /// 7. `[signer]` Payer + /// 8. `[]` System program + /// 9. `[]` Rent sysvar + /// 10. `[]` Clock sysvar CastVote { #[allow(dead_code)] /// Yes/No vote @@ -262,10 +263,11 @@ pub enum GovernanceInstruction { /// Finalizes vote in case the Vote was not automatically tipped within max_voting_time period /// - /// 0. `[]` Governance account - /// 1. `[writable]` Proposal account - /// 2. `[]` Governing Token Mint - /// 3. `[]` Clock sysvar + /// 0. `[]` Realm account + /// 1. `[]` Governance account + /// 2. `[writable]` Proposal account + /// 3. `[]` Governing Token Mint + /// 4. `[]` Clock sysvar FinalizeVote {}, /// Relinquish Vote removes voter weight from a Proposal and removes it from voter's active votes @@ -820,6 +822,7 @@ pub fn sign_off_proposal( pub fn cast_vote( program_id: &Pubkey, // Accounts + realm: &Pubkey, governance: &Pubkey, proposal: &Pubkey, token_owner_record: &Pubkey, @@ -832,6 +835,7 @@ pub fn cast_vote( let vote_record_address = get_vote_record_address(program_id, proposal, token_owner_record); let accounts = vec![ + AccountMeta::new_readonly(*realm, false), AccountMeta::new_readonly(*governance, false), AccountMeta::new(*proposal, false), AccountMeta::new(*token_owner_record, false), @@ -857,11 +861,13 @@ pub fn cast_vote( pub fn finalize_vote( program_id: &Pubkey, // Accounts + realm: &Pubkey, governance: &Pubkey, proposal: &Pubkey, governing_token_mint: &Pubkey, ) -> Instruction { let accounts = vec![ + AccountMeta::new_readonly(*realm, false), AccountMeta::new_readonly(*governance, false), AccountMeta::new(*proposal, false), AccountMeta::new_readonly(*governing_token_mint, false), diff --git a/governance/program/src/processor/process_cast_vote.rs b/governance/program/src/processor/process_cast_vote.rs index ac362164..83a11787 100644 --- a/governance/program/src/processor/process_cast_vote.rs +++ b/governance/program/src/processor/process_cast_vote.rs @@ -32,21 +32,22 @@ pub fn process_cast_vote( ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); - let governance_info = next_account_info(account_info_iter)?; // 0 - let proposal_info = next_account_info(account_info_iter)?; // 1 - let token_owner_record_info = next_account_info(account_info_iter)?; // 2 - let governance_authority_info = next_account_info(account_info_iter)?; // 3 + let _realm_info = next_account_info(account_info_iter)?; // 0 + let governance_info = next_account_info(account_info_iter)?; // 1 + let proposal_info = next_account_info(account_info_iter)?; // 2 + let token_owner_record_info = next_account_info(account_info_iter)?; // 3 + let governance_authority_info = next_account_info(account_info_iter)?; // 4 - let vote_record_info = next_account_info(account_info_iter)?; // 4 - let governing_token_mint_info = next_account_info(account_info_iter)?; // 5 + let vote_record_info = next_account_info(account_info_iter)?; // 5 + let governing_token_mint_info = next_account_info(account_info_iter)?; // 6 - let payer_info = next_account_info(account_info_iter)?; // 6 - let system_info = next_account_info(account_info_iter)?; // 7 + let payer_info = next_account_info(account_info_iter)?; // 7 + let system_info = next_account_info(account_info_iter)?; // 8 - let rent_sysvar_info = next_account_info(account_info_iter)?; // 8 + let rent_sysvar_info = next_account_info(account_info_iter)?; // 9 let rent = &Rent::from_account_info(rent_sysvar_info)?; - let clock_info = next_account_info(account_info_iter)?; // 9 + let clock_info = next_account_info(account_info_iter)?; // 10 let clock = Clock::from_account_info(clock_info)?; if !vote_record_info.data_is_empty() { diff --git a/governance/program/src/processor/process_create_realm.rs b/governance/program/src/processor/process_create_realm.rs index 771b0f31..f26ba22a 100644 --- a/governance/program/src/processor/process_create_realm.rs +++ b/governance/program/src/processor/process_create_realm.rs @@ -11,8 +11,10 @@ use solana_program::{ use crate::{ error::GovernanceError, state::{ - enums::GovernanceAccountType, - realm::{get_governing_token_holding_address_seeds, get_realm_address_seeds, Realm}, + enums::{GovernanceAccountType, MintMaxVoteWeightSource}, + realm::{ + get_governing_token_holding_address_seeds, get_realm_address_seeds, Realm, RealmConfig, + }, }, tools::{ account::create_and_serialize_account_signed, spl_token::create_spl_token_account_signed, @@ -82,10 +84,16 @@ pub fn process_create_realm( let realm_data = Realm { account_type: GovernanceAccountType::Realm, community_mint: *governance_token_mint_info.key, - council_mint: council_token_mint_address, + name: name.clone(), reserved: [0; 8], authority: Some(*realm_authority_info.key), + config: RealmConfig { + council_mint: council_token_mint_address, + reserved: [0; 8], + custodian: Some(*realm_authority_info.key), + community_mint_max_vote_weight_source: MintMaxVoteWeightSource::Percentage(100), + }, }; create_and_serialize_account_signed::( diff --git a/governance/program/src/processor/process_finalize_vote.rs b/governance/program/src/processor/process_finalize_vote.rs index bb2a375a..764cf825 100644 --- a/governance/program/src/processor/process_finalize_vote.rs +++ b/governance/program/src/processor/process_finalize_vote.rs @@ -22,12 +22,13 @@ use borsh::BorshSerialize; pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { let account_info_iter = &mut accounts.iter(); - let governance_info = next_account_info(account_info_iter)?; // 0 - let proposal_info = next_account_info(account_info_iter)?; // 1 + let _realm_info = next_account_info(account_info_iter)?; // 0 + let governance_info = next_account_info(account_info_iter)?; // 1 + let proposal_info = next_account_info(account_info_iter)?; // 2 - let governing_token_mint_info = next_account_info(account_info_iter)?; // 2 + let governing_token_mint_info = next_account_info(account_info_iter)?; // 3 - let clock_info = next_account_info(account_info_iter)?; // 3 + let clock_info = next_account_info(account_info_iter)?; // 4 let clock = Clock::from_account_info(clock_info)?; let governance_data = get_governance_data(program_id, governance_info)?; diff --git a/governance/program/src/state/enums.rs b/governance/program/src/state/enums.rs index e9ebf862..7ef4aa32 100644 --- a/governance/program/src/state/enums.rs +++ b/governance/program/src/state/enums.rs @@ -159,3 +159,17 @@ pub enum InstructionExecutionFlags { /// The implementation requires another account type to group instructions within a transaction UseTransaction, } + +/// The source of max vote weight used for voting +/// Values below 100% mint supply can be used when the governing token is fully minted but not distributed yet +/// Note: This field is not used yet. It's reserved for future versions +#[repr(C)] +#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] +pub enum MintMaxVoteWeightSource { + /// Percentage of the governing mint supply is used as max vote weight + /// The default is 100% to use all available mint supply for voting + Percentage(u8), + + /// Absolute value, irrelevant of the actual mint supply, is used as max vote weight + Absolute(u64), +} diff --git a/governance/program/src/state/realm.rs b/governance/program/src/state/realm.rs index 19673e58..486579ee 100644 --- a/governance/program/src/state/realm.rs +++ b/governance/program/src/state/realm.rs @@ -8,11 +8,32 @@ use solana_program::{ use crate::{ error::GovernanceError, + state::enums::{GovernanceAccountType, MintMaxVoteWeightSource}, tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize}, PROGRAM_AUTHORITY_SEED, }; -use crate::state::enums::GovernanceAccountType; +/// Realm Config defining Realm parameters. +#[repr(C)] +#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] +pub struct RealmConfig { + /// Optional council mint + pub council_mint: Option, + + /// The source used for community mint max vote weight source + /// Note: This field is not used yet. It's reserved for future versions + pub community_mint_max_vote_weight_source: MintMaxVoteWeightSource, + + /// An authority tasked with none critical and maintenance Realm operations + /// For example custodian authority is required to add governances to the Realm + /// There is no security risk with adding governances to the Realm but it should not be open for everybody + /// to prevent unrelated entries and noise + /// Note: This field is not used yet. It's reserved for future versions + pub custodian: Option, + + /// Reserved space for future versions + pub reserved: [u8; 8], +} /// Governance Realm Account /// Account PDA seeds" ['governance', name] @@ -25,13 +46,13 @@ pub struct Realm { /// Community mint pub community_mint: Pubkey, + /// Configuration of the Realm + pub config: RealmConfig, + /// Reserved space for future versions pub reserved: [u8; 8], - /// Council mint - pub council_mint: Option, - - /// Realm authority. The authority must sign transactions which update the realm (ex. adding governance, setting council) + /// Realm authority. The authority must sign transactions which update the realm config /// The authority can be transferer to Realm Governance and hence make the Realm self governed through proposals /// Note: This field is not used yet. It's reserved for future versions pub authority: Option, @@ -42,7 +63,7 @@ pub struct Realm { impl AccountMaxSize for Realm { fn get_max_size(&self) -> Option { - Some(self.name.len() + 111) + Some(self.name.len() + 161) } } @@ -62,7 +83,7 @@ impl Realm { return Ok(()); } - if self.council_mint == Some(*governing_token_mint) { + if self.config.council_mint == Some(*governing_token_mint) { return Ok(()); } @@ -184,9 +205,15 @@ mod test { account_type: GovernanceAccountType::Realm, community_mint: Pubkey::new_unique(), reserved: [0; 8], - council_mint: Some(Pubkey::new_unique()), + authority: Some(Pubkey::new_unique()), name: "test-realm".to_string(), + config: RealmConfig { + council_mint: Some(Pubkey::new_unique()), + reserved: [0; 8], + custodian: Some(Pubkey::new_unique()), + community_mint_max_vote_weight_source: MintMaxVoteWeightSource::Absolute(100), + }, }; let size = realm.try_to_vec().unwrap().len(); diff --git a/governance/program/src/state/token_owner_record.rs b/governance/program/src/state/token_owner_record.rs index 311aabbd..35f2641f 100644 --- a/governance/program/src/state/token_owner_record.rs +++ b/governance/program/src/state/token_owner_record.rs @@ -93,7 +93,7 @@ impl TokenOwnerRecord { let min_tokens_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.council_mint { + } else if Some(self.governing_token_mint) == realm_data.config.council_mint { config.min_council_tokens_to_create_proposal } else { return Err(GovernanceError::InvalidGoverningTokenMint.into()); diff --git a/governance/program/tests/process_cast_vote.rs b/governance/program/tests/process_cast_vote.rs index 701aad6f..57c84152 100644 --- a/governance/program/tests/process_cast_vote.rs +++ b/governance/program/tests/process_cast_vote.rs @@ -149,7 +149,8 @@ async fn test_cast_vote_with_invalid_mint_error() { .unwrap(); // Try to use Council Mint with Community Proposal - proposal_cookie.account.governing_token_mint = realm_cookie.account.council_mint.unwrap(); + proposal_cookie.account.governing_token_mint = + realm_cookie.account.config.council_mint.unwrap(); // Act let err = governance_test diff --git a/governance/program/tests/process_create_proposal.rs b/governance/program/tests/process_create_proposal.rs index c194cd1e..ccb90dcf 100644 --- a/governance/program/tests/process_create_proposal.rs +++ b/governance/program/tests/process_create_proposal.rs @@ -411,7 +411,7 @@ async fn test_create_council_proposal_using_community_tokens() { .await; assert_eq!( - realm_cookie.account.council_mint.unwrap(), + realm_cookie.account.config.council_mint.unwrap(), proposal_account.governing_token_mint ); diff --git a/governance/program/tests/process_finalize_vote.rs b/governance/program/tests/process_finalize_vote.rs index a86fbba9..ec635c29 100644 --- a/governance/program/tests/process_finalize_vote.rs +++ b/governance/program/tests/process_finalize_vote.rs @@ -72,7 +72,7 @@ async fn test_finalize_vote_to_succeeded() { // Act governance_test - .finalize_vote(&proposal_cookie) + .finalize_vote(&realm_cookie, &proposal_cookie) .await .unwrap(); @@ -151,7 +151,7 @@ async fn test_finalize_vote_to_defeated() { // Act governance_test - .finalize_vote(&proposal_cookie) + .finalize_vote(&realm_cookie, &proposal_cookie) .await .unwrap(); @@ -208,7 +208,7 @@ async fn test_finalize_vote_with_invalid_mint_error() { // Act let err = governance_test - .finalize_vote(&proposal_cookie) + .finalize_vote(&realm_cookie, &proposal_cookie) .await .err() .unwrap(); @@ -270,7 +270,7 @@ async fn test_finalize_vote_with_invalid_governance_error() { // Act let err = governance_test - .finalize_vote(&proposal_cookie) + .finalize_vote(&realm_cookie, &proposal_cookie) .await .err() .unwrap(); diff --git a/governance/program/tests/program_test/mod.rs b/governance/program/tests/program_test/mod.rs index 846c0242..01cc7ff6 100644 --- a/governance/program/tests/program_test/mod.rs +++ b/governance/program/tests/program_test/mod.rs @@ -36,7 +36,7 @@ use spl_governance::{ state::{ enums::{ GovernanceAccountType, InstructionExecutionFlags, InstructionExecutionStatus, - ProposalState, VoteThresholdPercentage, VoteWeight, + MintMaxVoteWeightSource, ProposalState, VoteThresholdPercentage, VoteWeight, }, governance::{ get_account_governance_address, get_mint_governance_address, @@ -47,7 +47,7 @@ use spl_governance::{ proposal_instruction::{ get_proposal_instruction_address, InstructionData, ProposalInstruction, }, - realm::{get_governing_token_holding_address, get_realm_address, Realm}, + realm::{get_governing_token_holding_address, get_realm_address, Realm, RealmConfig}, signatory_record::{get_signatory_record_address, SignatoryRecord}, token_owner_record::{get_token_owner_record_address, TokenOwnerRecord}, vote_record::{get_vote_record_address, VoteRecord}, @@ -185,10 +185,16 @@ impl GovernanceProgramTest { let account = Realm { account_type: GovernanceAccountType::Realm, community_mint: community_token_mint_keypair.pubkey(), - council_mint: Some(council_token_mint_keypair.pubkey()), + name, reserved: [0; 8], authority: Some(realm_authority.pubkey()), + config: RealmConfig { + council_mint: Some(council_token_mint_keypair.pubkey()), + reserved: [0; 8], + custodian: Some(realm_authority.pubkey()), + community_mint_max_vote_weight_source: MintMaxVoteWeightSource::Percentage(100), + }, }; RealmCookie { @@ -210,7 +216,7 @@ impl GovernanceProgramTest { self.next_realm_id += 1; let realm_address = get_realm_address(&self.program_id, &name); - let council_mint = realm_cookie.account.council_mint.unwrap(); + let council_mint = realm_cookie.account.config.council_mint.unwrap(); let realm_authority = Keypair::new(); @@ -230,10 +236,16 @@ impl GovernanceProgramTest { let account = Realm { account_type: GovernanceAccountType::Realm, community_mint: realm_cookie.account.community_mint, - council_mint: Some(council_mint), + name, reserved: [0; 8], authority: Some(realm_authority.pubkey()), + config: RealmConfig { + council_mint: Some(council_mint), + reserved: [0; 8], + custodian: Some(realm_authority.pubkey()), + community_mint_max_vote_weight_source: MintMaxVoteWeightSource::Percentage(100), + }, }; let community_token_holding_address = get_governing_token_holding_address( @@ -315,7 +327,7 @@ impl GovernanceProgramTest { ) { self.with_subsequent_governing_token_deposit( &realm_cookie.address, - &realm_cookie.account.council_mint.unwrap(), + &realm_cookie.account.config.council_mint.unwrap(), realm_cookie.council_mint_authority.as_ref().unwrap(), token_owner_record_cookie, amount, @@ -331,7 +343,7 @@ impl GovernanceProgramTest { ) -> TokeOwnerRecordCookie { self.with_initial_governing_token_deposit( &realm_cookie.address, - &realm_cookie.account.council_mint.unwrap(), + &realm_cookie.account.config.council_mint.unwrap(), &realm_cookie.council_mint_authority.as_ref().unwrap(), amount, ) @@ -345,7 +357,7 @@ impl GovernanceProgramTest { ) -> TokeOwnerRecordCookie { self.with_initial_governing_token_deposit( &realm_cookie.address, - &realm_cookie.account.council_mint.unwrap(), + &realm_cookie.account.config.council_mint.unwrap(), realm_cookie.council_mint_authority.as_ref().unwrap(), 100, ) @@ -502,7 +514,7 @@ impl GovernanceProgramTest { ) { self.with_governing_token_governance_delegate( realm_cookie, - &realm_cookie.account.council_mint.unwrap(), + &realm_cookie.account.config.council_mint.unwrap(), token_owner_record_cookie, ) .await; @@ -618,7 +630,7 @@ impl GovernanceProgramTest { self.withdraw_governing_tokens( realm_cookie, token_owner_record_cookie, - &realm_cookie.account.council_mint.unwrap(), + &realm_cookie.account.config.council_mint.unwrap(), &token_owner_record_cookie.token_owner, ) .await @@ -1263,10 +1275,12 @@ impl GovernanceProgramTest { #[allow(dead_code)] pub async fn finalize_vote( &mut self, + realm_cookie: &RealmCookie, proposal_cookie: &ProposalCookie, ) -> Result<(), ProgramError> { let finalize_vote_instruction = finalize_vote( &self.program_id, + &realm_cookie.address, &proposal_cookie.account.governance, &proposal_cookie.address, &proposal_cookie.account.governing_token_mint, @@ -1351,6 +1365,7 @@ impl GovernanceProgramTest { ) -> Result { let vote_instruction = cast_vote( &self.program_id, + &token_owner_record_cookie.account.realm, &proposal_cookie.account.governance, &proposal_cookie.address, &token_owner_record_cookie.address,