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
This commit is contained in:
Sebastian Bor 2021-07-29 17:04:05 +01:00 committed by GitHub
parent 60aeb7c56c
commit a74c5b998b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 131 additions and 58 deletions

2
Cargo.lock generated
View File

@ -3758,7 +3758,7 @@ dependencies = [
[[package]] [[package]]
name = "spl-governance" name = "spl-governance"
version = "1.0.4" version = "1.0.6"
dependencies = [ dependencies = [
"arrayref", "arrayref",
"assert_matches", "assert_matches",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "spl-governance" name = "spl-governance"
version = "1.0.4" version = "1.0.6"
description = "Solana Program Library Governance Program" description = "Solana Program Library Governance Program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library" repository = "https://github.com/solana-labs/solana-program-library"

View File

@ -244,16 +244,17 @@ pub enum GovernanceInstruction {
/// By doing so you indicate you approve or disapprove of running the Proposal set of instructions /// 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 /// If you tip the consensus then the instructions can begin to be run after their hold up time
/// ///
/// 0. `[]` Governance account /// 0. `[]` Realm account
/// 1. `[writable]` Proposal account /// 1. `[]` Governance account
/// 2. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner] /// 2. `[writable]` Proposal account
/// 3. `[signer]` Governance Authority (Token Owner or Governance Delegate) /// 3. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
/// 4. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record] /// 4. `[signer]` Governance Authority (Token Owner or Governance Delegate)
/// 5. `[]` Governing Token Mint /// 5. `[writable]` Proposal VoteRecord account. PDA seeds: ['governance',proposal,governing_token_owner_record]
/// 6. `[signer]` Payer /// 6. `[]` Governing Token Mint
/// 7. `[]` System program /// 7. `[signer]` Payer
/// 8. `[]` Rent sysvar /// 8. `[]` System program
/// 9. `[]` Clock sysvar /// 9. `[]` Rent sysvar
/// 10. `[]` Clock sysvar
CastVote { CastVote {
#[allow(dead_code)] #[allow(dead_code)]
/// Yes/No vote /// 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 /// Finalizes vote in case the Vote was not automatically tipped within max_voting_time period
/// ///
/// 0. `[]` Governance account /// 0. `[]` Realm account
/// 1. `[writable]` Proposal account /// 1. `[]` Governance account
/// 2. `[]` Governing Token Mint /// 2. `[writable]` Proposal account
/// 3. `[]` Clock sysvar /// 3. `[]` Governing Token Mint
/// 4. `[]` Clock sysvar
FinalizeVote {}, FinalizeVote {},
/// Relinquish Vote removes voter weight from a Proposal and removes it from voter's active votes /// 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( pub fn cast_vote(
program_id: &Pubkey, program_id: &Pubkey,
// Accounts // Accounts
realm: &Pubkey,
governance: &Pubkey, governance: &Pubkey,
proposal: &Pubkey, proposal: &Pubkey,
token_owner_record: &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 vote_record_address = get_vote_record_address(program_id, proposal, token_owner_record);
let accounts = vec![ let accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(*governance, false), AccountMeta::new_readonly(*governance, false),
AccountMeta::new(*proposal, false), AccountMeta::new(*proposal, false),
AccountMeta::new(*token_owner_record, false), AccountMeta::new(*token_owner_record, false),
@ -857,11 +861,13 @@ pub fn cast_vote(
pub fn finalize_vote( pub fn finalize_vote(
program_id: &Pubkey, program_id: &Pubkey,
// Accounts // Accounts
realm: &Pubkey,
governance: &Pubkey, governance: &Pubkey,
proposal: &Pubkey, proposal: &Pubkey,
governing_token_mint: &Pubkey, governing_token_mint: &Pubkey,
) -> Instruction { ) -> Instruction {
let accounts = vec![ let accounts = vec![
AccountMeta::new_readonly(*realm, false),
AccountMeta::new_readonly(*governance, false), AccountMeta::new_readonly(*governance, false),
AccountMeta::new(*proposal, false), AccountMeta::new(*proposal, false),
AccountMeta::new_readonly(*governing_token_mint, false), AccountMeta::new_readonly(*governing_token_mint, false),

View File

@ -32,21 +32,22 @@ pub fn process_cast_vote(
) -> ProgramResult { ) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
let governance_info = next_account_info(account_info_iter)?; // 0 let _realm_info = next_account_info(account_info_iter)?; // 0
let proposal_info = next_account_info(account_info_iter)?; // 1 let governance_info = next_account_info(account_info_iter)?; // 1
let token_owner_record_info = next_account_info(account_info_iter)?; // 2 let proposal_info = next_account_info(account_info_iter)?; // 2
let governance_authority_info = next_account_info(account_info_iter)?; // 3 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 vote_record_info = next_account_info(account_info_iter)?; // 5
let governing_token_mint_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 payer_info = next_account_info(account_info_iter)?; // 7
let system_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 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)?; let clock = Clock::from_account_info(clock_info)?;
if !vote_record_info.data_is_empty() { if !vote_record_info.data_is_empty() {

View File

@ -11,8 +11,10 @@ use solana_program::{
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
state::{ state::{
enums::GovernanceAccountType, enums::{GovernanceAccountType, MintMaxVoteWeightSource},
realm::{get_governing_token_holding_address_seeds, get_realm_address_seeds, Realm}, realm::{
get_governing_token_holding_address_seeds, get_realm_address_seeds, Realm, RealmConfig,
},
}, },
tools::{ tools::{
account::create_and_serialize_account_signed, spl_token::create_spl_token_account_signed, 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 { let realm_data = Realm {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::Realm,
community_mint: *governance_token_mint_info.key, community_mint: *governance_token_mint_info.key,
council_mint: council_token_mint_address,
name: name.clone(), name: name.clone(),
reserved: [0; 8], reserved: [0; 8],
authority: Some(*realm_authority_info.key), 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::<Realm>( create_and_serialize_account_signed::<Realm>(

View File

@ -22,12 +22,13 @@ use borsh::BorshSerialize;
pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
let governance_info = next_account_info(account_info_iter)?; // 0 let _realm_info = next_account_info(account_info_iter)?; // 0
let proposal_info = next_account_info(account_info_iter)?; // 1 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 clock = Clock::from_account_info(clock_info)?;
let governance_data = get_governance_data(program_id, governance_info)?; let governance_data = get_governance_data(program_id, governance_info)?;

View File

@ -159,3 +159,17 @@ pub enum InstructionExecutionFlags {
/// The implementation requires another account type to group instructions within a transaction /// The implementation requires another account type to group instructions within a transaction
UseTransaction, 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),
}

View File

@ -8,11 +8,32 @@ use solana_program::{
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
state::enums::{GovernanceAccountType, MintMaxVoteWeightSource},
tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize}, tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED, 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<Pubkey>,
/// 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<Pubkey>,
/// Reserved space for future versions
pub reserved: [u8; 8],
}
/// Governance Realm Account /// Governance Realm Account
/// Account PDA seeds" ['governance', name] /// Account PDA seeds" ['governance', name]
@ -25,13 +46,13 @@ pub struct Realm {
/// Community mint /// Community mint
pub community_mint: Pubkey, pub community_mint: Pubkey,
/// Configuration of the Realm
pub config: RealmConfig,
/// Reserved space for future versions /// Reserved space for future versions
pub reserved: [u8; 8], pub reserved: [u8; 8],
/// Council mint /// Realm authority. The authority must sign transactions which update the realm config
pub council_mint: Option<Pubkey>,
/// Realm authority. The authority must sign transactions which update the realm (ex. adding governance, setting council)
/// The authority can be transferer to Realm Governance and hence make the Realm self governed through proposals /// 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 /// Note: This field is not used yet. It's reserved for future versions
pub authority: Option<Pubkey>, pub authority: Option<Pubkey>,
@ -42,7 +63,7 @@ pub struct Realm {
impl AccountMaxSize for Realm { impl AccountMaxSize for Realm {
fn get_max_size(&self) -> Option<usize> { fn get_max_size(&self) -> Option<usize> {
Some(self.name.len() + 111) Some(self.name.len() + 161)
} }
} }
@ -62,7 +83,7 @@ impl Realm {
return Ok(()); return Ok(());
} }
if self.council_mint == Some(*governing_token_mint) { if self.config.council_mint == Some(*governing_token_mint) {
return Ok(()); return Ok(());
} }
@ -184,9 +205,15 @@ mod test {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::Realm,
community_mint: Pubkey::new_unique(), community_mint: Pubkey::new_unique(),
reserved: [0; 8], reserved: [0; 8],
council_mint: Some(Pubkey::new_unique()),
authority: Some(Pubkey::new_unique()), authority: Some(Pubkey::new_unique()),
name: "test-realm".to_string(), 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(); let size = realm.try_to_vec().unwrap().len();

View File

@ -93,7 +93,7 @@ impl TokenOwnerRecord {
let min_tokens_to_create_proposal = let min_tokens_to_create_proposal =
if self.governing_token_mint == realm_data.community_mint { if self.governing_token_mint == realm_data.community_mint {
config.min_community_tokens_to_create_proposal 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 config.min_council_tokens_to_create_proposal
} else { } else {
return Err(GovernanceError::InvalidGoverningTokenMint.into()); return Err(GovernanceError::InvalidGoverningTokenMint.into());

View File

@ -149,7 +149,8 @@ async fn test_cast_vote_with_invalid_mint_error() {
.unwrap(); .unwrap();
// Try to use Council Mint with Community Proposal // 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 // Act
let err = governance_test let err = governance_test

View File

@ -411,7 +411,7 @@ async fn test_create_council_proposal_using_community_tokens() {
.await; .await;
assert_eq!( assert_eq!(
realm_cookie.account.council_mint.unwrap(), realm_cookie.account.config.council_mint.unwrap(),
proposal_account.governing_token_mint proposal_account.governing_token_mint
); );

View File

@ -72,7 +72,7 @@ async fn test_finalize_vote_to_succeeded() {
// Act // Act
governance_test governance_test
.finalize_vote(&proposal_cookie) .finalize_vote(&realm_cookie, &proposal_cookie)
.await .await
.unwrap(); .unwrap();
@ -151,7 +151,7 @@ async fn test_finalize_vote_to_defeated() {
// Act // Act
governance_test governance_test
.finalize_vote(&proposal_cookie) .finalize_vote(&realm_cookie, &proposal_cookie)
.await .await
.unwrap(); .unwrap();
@ -208,7 +208,7 @@ async fn test_finalize_vote_with_invalid_mint_error() {
// Act // Act
let err = governance_test let err = governance_test
.finalize_vote(&proposal_cookie) .finalize_vote(&realm_cookie, &proposal_cookie)
.await .await
.err() .err()
.unwrap(); .unwrap();
@ -270,7 +270,7 @@ async fn test_finalize_vote_with_invalid_governance_error() {
// Act // Act
let err = governance_test let err = governance_test
.finalize_vote(&proposal_cookie) .finalize_vote(&realm_cookie, &proposal_cookie)
.await .await
.err() .err()
.unwrap(); .unwrap();

View File

@ -36,7 +36,7 @@ use spl_governance::{
state::{ state::{
enums::{ enums::{
GovernanceAccountType, InstructionExecutionFlags, InstructionExecutionStatus, GovernanceAccountType, InstructionExecutionFlags, InstructionExecutionStatus,
ProposalState, VoteThresholdPercentage, VoteWeight, MintMaxVoteWeightSource, ProposalState, VoteThresholdPercentage, VoteWeight,
}, },
governance::{ governance::{
get_account_governance_address, get_mint_governance_address, get_account_governance_address, get_mint_governance_address,
@ -47,7 +47,7 @@ use spl_governance::{
proposal_instruction::{ proposal_instruction::{
get_proposal_instruction_address, InstructionData, ProposalInstruction, 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}, signatory_record::{get_signatory_record_address, SignatoryRecord},
token_owner_record::{get_token_owner_record_address, TokenOwnerRecord}, token_owner_record::{get_token_owner_record_address, TokenOwnerRecord},
vote_record::{get_vote_record_address, VoteRecord}, vote_record::{get_vote_record_address, VoteRecord},
@ -185,10 +185,16 @@ impl GovernanceProgramTest {
let account = Realm { let account = Realm {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::Realm,
community_mint: community_token_mint_keypair.pubkey(), community_mint: community_token_mint_keypair.pubkey(),
council_mint: Some(council_token_mint_keypair.pubkey()),
name, name,
reserved: [0; 8], reserved: [0; 8],
authority: Some(realm_authority.pubkey()), 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 { RealmCookie {
@ -210,7 +216,7 @@ impl GovernanceProgramTest {
self.next_realm_id += 1; self.next_realm_id += 1;
let realm_address = get_realm_address(&self.program_id, &name); 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(); let realm_authority = Keypair::new();
@ -230,10 +236,16 @@ impl GovernanceProgramTest {
let account = Realm { let account = Realm {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::Realm,
community_mint: realm_cookie.account.community_mint, community_mint: realm_cookie.account.community_mint,
council_mint: Some(council_mint),
name, name,
reserved: [0; 8], reserved: [0; 8],
authority: Some(realm_authority.pubkey()), 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( let community_token_holding_address = get_governing_token_holding_address(
@ -315,7 +327,7 @@ impl GovernanceProgramTest {
) { ) {
self.with_subsequent_governing_token_deposit( self.with_subsequent_governing_token_deposit(
&realm_cookie.address, &realm_cookie.address,
&realm_cookie.account.council_mint.unwrap(), &realm_cookie.account.config.council_mint.unwrap(),
realm_cookie.council_mint_authority.as_ref().unwrap(), realm_cookie.council_mint_authority.as_ref().unwrap(),
token_owner_record_cookie, token_owner_record_cookie,
amount, amount,
@ -331,7 +343,7 @@ impl GovernanceProgramTest {
) -> TokeOwnerRecordCookie { ) -> TokeOwnerRecordCookie {
self.with_initial_governing_token_deposit( self.with_initial_governing_token_deposit(
&realm_cookie.address, &realm_cookie.address,
&realm_cookie.account.council_mint.unwrap(), &realm_cookie.account.config.council_mint.unwrap(),
&realm_cookie.council_mint_authority.as_ref().unwrap(), &realm_cookie.council_mint_authority.as_ref().unwrap(),
amount, amount,
) )
@ -345,7 +357,7 @@ impl GovernanceProgramTest {
) -> TokeOwnerRecordCookie { ) -> TokeOwnerRecordCookie {
self.with_initial_governing_token_deposit( self.with_initial_governing_token_deposit(
&realm_cookie.address, &realm_cookie.address,
&realm_cookie.account.council_mint.unwrap(), &realm_cookie.account.config.council_mint.unwrap(),
realm_cookie.council_mint_authority.as_ref().unwrap(), realm_cookie.council_mint_authority.as_ref().unwrap(),
100, 100,
) )
@ -502,7 +514,7 @@ impl GovernanceProgramTest {
) { ) {
self.with_governing_token_governance_delegate( self.with_governing_token_governance_delegate(
realm_cookie, realm_cookie,
&realm_cookie.account.council_mint.unwrap(), &realm_cookie.account.config.council_mint.unwrap(),
token_owner_record_cookie, token_owner_record_cookie,
) )
.await; .await;
@ -618,7 +630,7 @@ impl GovernanceProgramTest {
self.withdraw_governing_tokens( self.withdraw_governing_tokens(
realm_cookie, realm_cookie,
token_owner_record_cookie, token_owner_record_cookie,
&realm_cookie.account.council_mint.unwrap(), &realm_cookie.account.config.council_mint.unwrap(),
&token_owner_record_cookie.token_owner, &token_owner_record_cookie.token_owner,
) )
.await .await
@ -1263,10 +1275,12 @@ impl GovernanceProgramTest {
#[allow(dead_code)] #[allow(dead_code)]
pub async fn finalize_vote( pub async fn finalize_vote(
&mut self, &mut self,
realm_cookie: &RealmCookie,
proposal_cookie: &ProposalCookie, proposal_cookie: &ProposalCookie,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
let finalize_vote_instruction = finalize_vote( let finalize_vote_instruction = finalize_vote(
&self.program_id, &self.program_id,
&realm_cookie.address,
&proposal_cookie.account.governance, &proposal_cookie.account.governance,
&proposal_cookie.address, &proposal_cookie.address,
&proposal_cookie.account.governing_token_mint, &proposal_cookie.account.governing_token_mint,
@ -1351,6 +1365,7 @@ impl GovernanceProgramTest {
) -> Result<VoteRecordCookie, ProgramError> { ) -> Result<VoteRecordCookie, ProgramError> {
let vote_instruction = cast_vote( let vote_instruction = cast_vote(
&self.program_id, &self.program_id,
&token_owner_record_cookie.account.realm,
&proposal_cookie.account.governance, &proposal_cookie.account.governance,
&proposal_cookie.address, &proposal_cookie.address,
&token_owner_record_cookie.address, &token_owner_record_cookie.address,