use std::slice::Iter;
use solana_program::account_info::next_account_info;
use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::tools::structs::Reserved110;
use crate::{error::GovernanceError, state::enums::GovernanceAccountType};
use crate::state::realm::GoverningTokenConfigArgs;
use crate::state::realm::{RealmConfigArgs, RealmV2};
#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum GoverningTokenType {
Liquid,
Membership,
Dormant,
}
#[allow(clippy::derivable_impls)]
impl Default for GoverningTokenType {
fn default() -> Self {
GoverningTokenType::Liquid
}
}
#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema, Default)]
pub struct GoverningTokenConfig {
pub voter_weight_addin: Option<Pubkey>,
pub max_voter_weight_addin: Option<Pubkey>,
pub token_type: GoverningTokenType,
pub reserved: [u8; 8],
}
#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfigAccount {
pub account_type: GovernanceAccountType,
pub realm: Pubkey,
pub community_token_config: GoverningTokenConfig,
pub council_token_config: GoverningTokenConfig,
pub reserved: Reserved110,
}
impl AccountMaxSize for RealmConfigAccount {
fn get_max_size(&self) -> Option<usize> {
Some(1 + 32 + 75 * 2 + 110)
}
}
impl IsInitialized for RealmConfigAccount {
fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::RealmConfig
}
}
impl RealmConfigAccount {
pub fn get_token_config(
&self,
realm_data: &RealmV2,
governing_token_mint: &Pubkey,
) -> Result<&GoverningTokenConfig, ProgramError> {
let token_config = if *governing_token_mint == realm_data.community_mint {
&self.community_token_config
} else if Some(*governing_token_mint) == realm_data.config.council_mint {
&self.council_token_config
} else {
return Err(GovernanceError::InvalidGoverningTokenMint.into());
};
Ok(token_config)
}
pub fn assert_can_revoke_governing_token(
&self,
realm_data: &RealmV2,
governing_token_mint: &Pubkey,
) -> Result<(), ProgramError> {
let governing_token_type = &self
.get_token_config(realm_data, governing_token_mint)?
.token_type;
match governing_token_type {
GoverningTokenType::Membership => Ok(()),
GoverningTokenType::Liquid | GoverningTokenType::Dormant => {
Err(GovernanceError::CannotRevokeGoverningTokens.into())
}
}
}
pub fn assert_can_deposit_governing_token(
&self,
realm_data: &RealmV2,
governing_token_mint: &Pubkey,
) -> Result<(), ProgramError> {
let governing_token_type = &self
.get_token_config(realm_data, governing_token_mint)?
.token_type;
match governing_token_type {
GoverningTokenType::Membership | GoverningTokenType::Liquid => Ok(()),
GoverningTokenType::Dormant => Err(GovernanceError::CannotDepositDormantTokens.into()),
}
}
pub fn assert_can_withdraw_governing_token(
&self,
realm_data: &RealmV2,
governing_token_mint: &Pubkey,
) -> Result<(), ProgramError> {
let governing_token_type = &self
.get_token_config(realm_data, governing_token_mint)?
.token_type;
match governing_token_type {
GoverningTokenType::Dormant | GoverningTokenType::Liquid => Ok(()),
GoverningTokenType::Membership => {
Err(GovernanceError::CannotWithdrawMembershipTokens.into())
}
}
}
pub fn assert_can_change_config(
&self,
realm_config_args: &RealmConfigArgs,
) -> Result<(), ProgramError> {
if self.community_token_config.token_type != GoverningTokenType::Membership
&& realm_config_args.community_token_config_args.token_type
== GoverningTokenType::Membership
{
return Err(GovernanceError::CannotChangeCommunityTokenTypeToMembership.into());
}
Ok(())
}
}
pub fn get_realm_config_data(
program_id: &Pubkey,
realm_config_info: &AccountInfo,
) -> Result<RealmConfigAccount, ProgramError> {
get_account_data::<RealmConfigAccount>(program_id, realm_config_info)
}
pub fn get_realm_config_data_for_realm(
program_id: &Pubkey,
realm_config_info: &AccountInfo,
realm: &Pubkey,
) -> Result<RealmConfigAccount, ProgramError> {
let realm_config_data = if realm_config_info.data_is_empty() {
let realm_config_address = get_realm_config_address(program_id, realm);
if realm_config_address != *realm_config_info.key {
return Err(GovernanceError::InvalidRealmConfigAddress.into());
}
RealmConfigAccount {
account_type: GovernanceAccountType::RealmConfig,
realm: *realm,
community_token_config: GoverningTokenConfig::default(),
council_token_config: GoverningTokenConfig::default(),
reserved: Reserved110::default(),
}
} else {
let realm_config_data = get_realm_config_data(program_id, realm_config_info)?;
if realm_config_data.realm != *realm {
return Err(GovernanceError::InvalidRealmConfigForRealm.into());
}
realm_config_data
};
Ok(realm_config_data)
}
pub fn get_realm_config_address_seeds(realm: &Pubkey) -> [&[u8]; 2] {
[b"realm-config", realm.as_ref()]
}
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
}
pub fn resolve_governing_token_config(
account_info_iter: &mut Iter<AccountInfo>,
governing_token_config_args: &GoverningTokenConfigArgs,
) -> Result<GoverningTokenConfig, ProgramError> {
let voter_weight_addin = if governing_token_config_args.use_voter_weight_addin {
let voter_weight_addin_info = next_account_info(account_info_iter)?;
Some(*voter_weight_addin_info.key)
} else {
None
};
let max_voter_weight_addin = if governing_token_config_args.use_max_voter_weight_addin {
let max_voter_weight_addin_info = next_account_info(account_info_iter)?;
Some(*max_voter_weight_addin_info.key)
} else {
None
};
Ok(GoverningTokenConfig {
voter_weight_addin,
max_voter_weight_addin,
token_type: governing_token_config_args.token_type.clone(),
reserved: [0; 8],
})
}
#[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::RealmV2,
realm: Pubkey::new_unique(),
community_token_config: GoverningTokenConfig {
voter_weight_addin: Some(Pubkey::new_unique()),
max_voter_weight_addin: Some(Pubkey::new_unique()),
token_type: GoverningTokenType::Liquid,
reserved: [0; 8],
},
council_token_config: GoverningTokenConfig {
voter_weight_addin: Some(Pubkey::new_unique()),
max_voter_weight_addin: Some(Pubkey::new_unique()),
token_type: GoverningTokenType::Liquid,
reserved: [0; 8],
},
reserved: Reserved110::default(),
};
let size = realm_config.try_to_vec().unwrap().len();
assert_eq!(realm_config.get_max_size(), Some(size));
}
}