use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use gemachain_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use gpl_governance_tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize};
use crate::{
error::GovernanceError,
state::enums::{GovernanceAccountType, MintMaxVoteWeightSource},
PROGRAM_AUTHORITY_SEED,
};
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfigArgs {
pub use_council_mint: bool,
pub min_community_tokens_to_create_governance: u64,
pub community_mint_max_vote_weight_source: MintMaxVoteWeightSource,
pub use_community_voter_weight_addin: bool,
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfig {
pub use_community_voter_weight_addin: bool,
pub reserved: [u8; 7],
pub min_community_tokens_to_create_governance: u64,
pub community_mint_max_vote_weight_source: MintMaxVoteWeightSource,
pub council_mint: Option<Pubkey>,
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct Realm {
pub account_type: GovernanceAccountType,
pub community_mint: Pubkey,
pub config: RealmConfig,
pub reserved: [u8; 8],
pub authority: Option<Pubkey>,
pub name: String,
}
impl AccountMaxSize for Realm {
fn get_max_size(&self) -> Option<usize> {
Some(self.name.len() + 136)
}
}
impl IsInitialized for Realm {
fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::Realm
}
}
impl Realm {
pub fn assert_is_valid_governing_token_mint(
&self,
governing_token_mint: &Pubkey,
) -> Result<(), ProgramError> {
if self.community_mint == *governing_token_mint {
return Ok(());
}
if self.config.council_mint == Some(*governing_token_mint) {
return Ok(());
}
Err(GovernanceError::InvalidGoverningTokenMint.into())
}
pub fn assert_is_valid_governing_token_mint_and_holding(
&self,
program_id: &Pubkey,
realm: &Pubkey,
governing_token_mint: &Pubkey,
governing_token_holding: &Pubkey,
) -> Result<(), ProgramError> {
self.assert_is_valid_governing_token_mint(governing_token_mint)?;
let governing_token_holding_address =
get_governing_token_holding_address(program_id, realm, governing_token_mint);
if governing_token_holding_address != *governing_token_holding {
return Err(GovernanceError::InvalidGoverningTokenHoldingAccount.into());
}
Ok(())
}
pub fn asset_governing_tokens_deposits_allowed(
&self,
governing_token_mint: &Pubkey,
) -> Result<(), ProgramError> {
if self.config.use_community_voter_weight_addin
&& self.community_mint == *governing_token_mint
{
return Err(GovernanceError::GoverningTokenDepositsNotAllowed.into());
}
Ok(())
}
}
pub fn assert_is_valid_realm(
program_id: &Pubkey,
realm_info: &AccountInfo,
) -> Result<(), ProgramError> {
assert_is_valid_account(realm_info, GovernanceAccountType::Realm, program_id)
}
pub fn get_realm_data(
program_id: &Pubkey,
realm_info: &AccountInfo,
) -> Result<Realm, ProgramError> {
get_account_data::<Realm>(program_id, realm_info)
}
pub fn get_realm_data_for_authority(
program_id: &Pubkey,
realm_info: &AccountInfo,
realm_authority: &Pubkey,
) -> Result<Realm, ProgramError> {
let realm_data = get_account_data::<Realm>(program_id, realm_info)?;
if realm_data.authority.is_none() {
return Err(GovernanceError::RealmHasNoAuthority.into());
}
if realm_data.authority.unwrap() != *realm_authority {
return Err(GovernanceError::InvalidAuthorityForRealm.into());
}
Ok(realm_data)
}
pub fn get_realm_data_for_governing_token_mint(
program_id: &Pubkey,
realm_info: &AccountInfo,
governing_token_mint: &Pubkey,
) -> Result<Realm, ProgramError> {
let realm_data = get_realm_data(program_id, realm_info)?;
realm_data.assert_is_valid_governing_token_mint(governing_token_mint)?;
Ok(realm_data)
}
pub fn get_realm_address_seeds(name: &str) -> [&[u8]; 2] {
[PROGRAM_AUTHORITY_SEED, name.as_bytes()]
}
pub fn get_realm_address(program_id: &Pubkey, name: &str) -> Pubkey {
Pubkey::find_program_address(&get_realm_address_seeds(name), program_id).0
}
pub fn get_governing_token_holding_address_seeds<'a>(
realm: &'a Pubkey,
governing_token_mint: &'a Pubkey,
) -> [&'a [u8]; 3] {
[
PROGRAM_AUTHORITY_SEED,
realm.as_ref(),
governing_token_mint.as_ref(),
]
}
pub fn get_governing_token_holding_address(
program_id: &Pubkey,
realm: &Pubkey,
governing_token_mint: &Pubkey,
) -> Pubkey {
Pubkey::find_program_address(
&get_governing_token_holding_address_seeds(realm, governing_token_mint),
program_id,
)
.0
}
pub fn assert_valid_realm_config_args(config_args: &RealmConfigArgs) -> Result<(), ProgramError> {
match config_args.community_mint_max_vote_weight_source {
MintMaxVoteWeightSource::SupplyFraction(fraction) => {
if !(1..=MintMaxVoteWeightSource::SUPPLY_FRACTION_BASE).contains(&fraction) {
return Err(GovernanceError::InvalidMaxVoteWeightSupplyFraction.into());
}
}
MintMaxVoteWeightSource::Absolute(_) => {
return Err(GovernanceError::MintMaxVoteWeightSourceNotSupported.into())
}
}
Ok(())
}
#[cfg(test)]
mod test {
use crate::instruction::GovernanceInstruction;
use gemachain_program::borsh::try_from_slice_unchecked;
use super::*;
#[test]
fn test_max_size() {
let realm = Realm {
account_type: GovernanceAccountType::Realm,
community_mint: Pubkey::new_unique(),
reserved: [0; 8],
authority: Some(Pubkey::new_unique()),
name: "test-realm".to_string(),
config: RealmConfig {
council_mint: Some(Pubkey::new_unique()),
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,
},
};
let size = realm.try_to_vec().unwrap().len();
assert_eq!(realm.get_max_size(), Some(size));
}
#[test]
fn test_deserialize_v2_realm_account_from_v1() {
let realm_v1 = gpl_governance_v1::state::realm::Realm {
account_type: gpl_governance_v1::state::enums::GovernanceAccountType::Realm,
community_mint: Pubkey::new_unique(),
config: gpl_governance_v1::state::realm::RealmConfig {
council_mint: Some(Pubkey::new_unique()),
reserved: [0; 8],
community_mint_max_vote_weight_source:
gpl_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();
let realm_v2: Realm = try_from_slice_unchecked(&realm_v1_data).unwrap();
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() {
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();
let create_realm_ix_v1: gpl_governance_v1::instruction::GovernanceInstruction =
try_from_slice_unchecked(&create_realm_ix_data).unwrap();
if let gpl_governance_v1::instruction::GovernanceInstruction::CreateRealm {
name,
config_args,
} = create_realm_ix_v1
{
assert_eq!("test-realm", name);
assert_eq!(
gpl_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");
}
}
}