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:
parent
60aeb7c56c
commit
a74c5b998b
|
@ -3758,7 +3758,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "spl-governance"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"assert_matches",
|
||||
|
|
|
@ -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 <maintainers@solana.foundation>"]
|
||||
repository = "https://github.com/solana-labs/solana-program-library"
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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::<Realm>(
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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<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
|
||||
/// 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<Pubkey>,
|
||||
|
||||
/// 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<Pubkey>,
|
||||
|
@ -42,7 +63,7 @@ pub struct Realm {
|
|||
|
||||
impl AccountMaxSize for Realm {
|
||||
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(());
|
||||
}
|
||||
|
||||
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();
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<VoteRecordCookie, ProgramError> {
|
||||
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,
|
||||
|
|
Loading…
Reference in New Issue