use crate::{ parse_account_data::{ParsableAccount, ParseAccountError}, validator_info, }; use bincode::deserialize; use serde_json::Value; use solana_config_program::{get_config_data, ConfigKeys}; use solana_sdk::pubkey::Pubkey; use solana_stake_program::config::Config as StakeConfig; pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result { let parsed_account = if pubkey == &solana_stake_program::config::id() { get_config_data(data) .ok() .and_then(|data| deserialize::(data).ok()) .map(|config| ConfigAccountType::StakeConfig(config.into())) } else { deserialize::(data).ok().and_then(|key_list| { if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() { parse_config_data::(data, key_list.keys).and_then(|validator_info| { Some(ConfigAccountType::ValidatorInfo(UiConfig { keys: validator_info.keys, config_data: serde_json::from_str(&validator_info.config_data).ok()?, })) }) } else { None } }) }; parsed_account.ok_or(ParseAccountError::AccountNotParsable( ParsableAccount::Config, )) } fn parse_config_data(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option> where T: serde::de::DeserializeOwned, { let config_data: T = deserialize(&get_config_data(data).ok()?).ok()?; let keys = keys .iter() .map(|key| UiConfigKey { pubkey: key.0.to_string(), signer: key.1, }) .collect(); Some(UiConfig { keys, config_data }) } #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase", tag = "type", content = "info")] pub enum ConfigAccountType { StakeConfig(UiStakeConfig), ValidatorInfo(UiConfig), } #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UiConfigKey { pub pubkey: String, pub signer: bool, } #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UiStakeConfig { pub warmup_cooldown_rate: f64, pub slash_penalty: u8, } impl From for UiStakeConfig { fn from(config: StakeConfig) -> Self { Self { warmup_cooldown_rate: config.warmup_cooldown_rate, slash_penalty: config.slash_penalty, } } } #[derive(Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UiConfig { pub keys: Vec, pub config_data: T, } #[cfg(test)] mod test { use super::*; use crate::validator_info::ValidatorInfo; use serde_json::json; use solana_config_program::create_config_account; use solana_sdk::account::ReadableAccount; #[test] fn test_parse_config() { let stake_config = StakeConfig { warmup_cooldown_rate: 0.25, slash_penalty: 50, }; let stake_config_account = create_config_account(vec![], &stake_config, 10); assert_eq!( parse_config( &stake_config_account.data(), &solana_stake_program::config::id() ) .unwrap(), ConfigAccountType::StakeConfig(UiStakeConfig { warmup_cooldown_rate: 0.25, slash_penalty: 50, }), ); let validator_info = ValidatorInfo { info: serde_json::to_string(&json!({ "name": "Solana", })) .unwrap(), }; let info_pubkey = solana_sdk::pubkey::new_rand(); let validator_info_config_account = create_config_account( vec![(validator_info::id(), false), (info_pubkey, true)], &validator_info, 10, ); assert_eq!( parse_config(&validator_info_config_account.data(), &info_pubkey).unwrap(), ConfigAccountType::ValidatorInfo(UiConfig { keys: vec![ UiConfigKey { pubkey: validator_info::id().to_string(), signer: false, }, UiConfigKey { pubkey: info_pubkey.to_string(), signer: true, } ], config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(), }), ); let bad_data = vec![0; 4]; assert!(parse_config(&bad_data, &info_pubkey).is_err()); } }