1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
//! RealmConfig account
use {
crate::{
error::GovernanceError,
state::{
enums::GovernanceAccountType,
realm::{GoverningTokenConfigArgs, RealmConfigArgs, RealmV2},
},
tools::structs::Reserved110,
},
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
solana_program::{
account_info::{next_account_info, AccountInfo},
program_error::ProgramError,
program_pack::IsInitialized,
pubkey::Pubkey,
},
spl_governance_tools::account::{get_account_data, AccountMaxSize},
std::slice::Iter,
};
/// The type of the governing token defines:
/// 1) Who retains the authority over deposited tokens
/// 2) Which token instructions Deposit, Withdraw and Revoke (burn) are allowed
#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum GoverningTokenType {
/// Liquid token is a token which is fully liquid and the token owner
/// retains full authority over it.
/// Deposit - Yes
/// Withdraw - Yes
/// Revoke - No, Realm authority cannot revoke liquid tokens
Liquid,
/// Membership token is a token controlled by Realm authority
/// Deposit - Yes, membership tokens can be deposited to gain governance
/// power.
/// The membership tokens are conventionally minted into the holding
/// account to keep them out of members possession.
/// Withdraw - No, after membership tokens are deposited they are no longer
/// transferable and can't be withdrawn.
/// Revoke - Yes, Realm authority can Revoke (burn) membership tokens.
Membership,
/// Dormant token is a token which is only a placeholder and its deposits
/// are not accepted and not used for governance power within the Realm
///
/// The Dormant token type is used when only a single voting population is
/// operational. For example a Multisig starter DAO uses Council only
/// and sets Community as Dormant to indicate its not utilized for any
/// governance power. Once the starter DAO decides to decentralise then
/// it can change the Community token to Liquid
///
/// Note: When an external voter weight plugin which takes deposits of the
/// token is used then the type should be set to Dormant to make the
/// intention explicit
///
/// Deposit - No, dormant tokens can't be deposited into the Realm
/// Withdraw - Yes, tokens can still be withdrawn from Realm to support
/// scenario where the config is changed while some tokens are still
/// deposited.
/// Revoke - No, Realm authority cannot revoke dormant tokens
Dormant,
}
#[allow(clippy::derivable_impls)]
impl Default for GoverningTokenType {
fn default() -> Self {
GoverningTokenType::Liquid
}
}
/// GoverningTokenConfig specifies configuration for Realm governing token
/// (Community or Council)
#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema, Default)]
pub struct GoverningTokenConfig {
/// Plugin providing voter weights for the governing token
pub voter_weight_addin: Option<Pubkey>,
/// Plugin providing max voter weight for the governing token
pub max_voter_weight_addin: Option<Pubkey>,
/// Governing token type
pub token_type: GoverningTokenType,
/// Reserved space for future versions
pub reserved: [u8; 8],
}
/// RealmConfig account
/// The account is an optional extension to RealmConfig stored on Realm account
#[derive(Clone, Debug, PartialEq, Eq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfigAccount {
/// Governance account type
pub account_type: GovernanceAccountType,
/// The realm the config belong to
pub realm: Pubkey,
/// Community token config
pub community_token_config: GoverningTokenConfig,
/// Council token config
pub council_token_config: GoverningTokenConfig,
/// Reserved
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 {
/// Returns GoverningTokenConfig for the given governing_token_mint
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)
}
/// Asserts the given governing token can be revoked
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())
}
}
}
/// Asserts the given governing token can be deposited
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(()),
// Note: Preventing deposits of the Dormant type tokens is not a direct security concern
// It only makes the intention of not using deposited tokens as governance power
// stronger
GoverningTokenType::Dormant => Err(GovernanceError::CannotDepositDormantTokens.into()),
}
}
/// Asserts the given governing token can be withdrawn
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())
}
}
}
/// Asserts the given RealmConfigArgs represent a valid Realm configuration
/// change
pub fn assert_can_change_config(
&self,
realm_config_args: &RealmConfigArgs,
) -> Result<(), ProgramError> {
// Existing community token type can't be changed to Membership because it would
// give the Realm authority the right to burn members tokens which should not be
// the case because the tokens belong to the members On the other had
// for the Council token it's acceptable and in fact desired change because
// council tokens denote membership which should be controlled by the
// Realm
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(())
}
}
/// Deserializes RealmConfig account and checks owner program
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)
}
/// If the account exists then deserializes it into RealmConfigAccount struct
/// and checks the owner program and the Realm it belongs to If the account
/// doesn't exist then it checks its address is derived from the given owner
/// program and Realm and returns default RealmConfigAccount
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() {
// If RealmConfigAccount doesn't exist yet then validate its PDA
// PDA validation is required because RealmConfigAccount might not exist for
// legacy Realms and then its absence is used as default
// RealmConfigAccount value with no plugins and Liquid governance tokens
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)
}
/// Returns RealmConfig PDA seeds
pub fn get_realm_config_address_seeds(realm: &Pubkey) -> [&[u8]; 2] {
[b"realm-config", realm.as_ref()]
}
/// Returns RealmConfig PDA address
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
}
/// Resolves GoverningTokenConfig from GoverningTokenConfigArgs and instruction
/// accounts
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::*,
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));
}
}