solana_account_decoder_wasm/
parse_config.rs

1use bincode::deserialize;
2use serde::Deserialize;
3use serde::Serialize;
4use serde_json::Value;
5use solana_config_program_client::ConfigKeys;
6use solana_config_program_client::get_config_data;
7use solana_pubkey::Pubkey;
8use solana_stake_interface::config::Config as StakeConfig;
9use solana_stake_interface::config::{self as stake_config};
10
11use crate::parse_account_data::ParsableAccount;
12use crate::parse_account_data::ParseAccountError;
13use crate::validator_info;
14
15pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
16	let parsed_account = if pubkey == &stake_config::id() {
17		get_config_data(data)
18			.ok()
19			.and_then(|data| deserialize::<StakeConfig>(data).ok())
20			.map(|config| ConfigAccountType::StakeConfig(config.into()))
21	} else {
22		deserialize::<ConfigKeys>(data).ok().and_then(|key_list| {
23			if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() {
24				parse_config_data::<String>(data, key_list.keys).and_then(|validator_info| {
25					Some(ConfigAccountType::ValidatorInfo(UiConfig {
26						keys: validator_info.keys,
27						config_data: serde_json::from_str(&validator_info.config_data).ok()?,
28					}))
29				})
30			} else {
31				None
32			}
33		})
34	};
35	parsed_account.ok_or(ParseAccountError::AccountNotParsable(
36		ParsableAccount::Config,
37	))
38}
39
40fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConfig<T>>
41where
42	T: serde::de::DeserializeOwned,
43{
44	let config_data: T = deserialize(get_config_data(data).ok()?).ok()?;
45	let keys = keys
46		.iter()
47		.map(|key| {
48			UiConfigKey {
49				pubkey: key.0.to_string(),
50				signer: key.1,
51			}
52		})
53		.collect();
54	Some(UiConfig { keys, config_data })
55}
56
57#[derive(Debug, Serialize, Deserialize, PartialEq)]
58#[serde(rename_all = "camelCase", tag = "type", content = "info")]
59pub enum ConfigAccountType {
60	StakeConfig(UiStakeConfig),
61	ValidatorInfo(UiConfig<Value>),
62}
63
64#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
65#[serde(rename_all = "camelCase")]
66pub struct UiConfigKey {
67	pub pubkey: String,
68	pub signer: bool,
69}
70
71#[deprecated(
72	since = "1.16.7",
73	note = "Please use `solana_stake_interface::state::warmup_cooldown_rate()` instead"
74)]
75#[derive(Debug, Serialize, Deserialize, PartialEq)]
76#[serde(rename_all = "camelCase")]
77pub struct UiStakeConfig {
78	pub warmup_cooldown_rate: f64,
79	pub slash_penalty: u8,
80}
81
82impl From<StakeConfig> for UiStakeConfig {
83	fn from(config: StakeConfig) -> Self {
84		Self {
85			warmup_cooldown_rate: config.warmup_cooldown_rate,
86			slash_penalty: config.slash_penalty,
87		}
88	}
89}
90
91#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
92#[serde(rename_all = "camelCase")]
93pub struct UiConfig<T> {
94	pub keys: Vec<UiConfigKey>,
95	pub config_data: T,
96}
97
98#[cfg(test)]
99mod test {
100	use bincode::serialize;
101	use serde_json::json;
102	use solana_account::Account;
103	use solana_account::AccountSharedData;
104	use solana_account::ReadableAccount;
105	use solana_config_program_client::ConfigKeys;
106
107	use super::*;
108	use crate::validator_info::ValidatorInfo;
109
110	fn create_config_account<T: serde::Serialize>(
111		keys: Vec<(Pubkey, bool)>,
112		config_data: &T,
113		lamports: u64,
114	) -> AccountSharedData {
115		let mut data = serialize(&ConfigKeys { keys }).unwrap();
116		data.extend_from_slice(&serialize(config_data).unwrap());
117		AccountSharedData::from(Account {
118			lamports,
119			data,
120			owner: solana_sdk_ids::config::id(),
121			..Account::default()
122		})
123	}
124
125	#[test]
126	fn test_parse_config() {
127		let stake_config = StakeConfig {
128			warmup_cooldown_rate: 0.25,
129			slash_penalty: 50,
130		};
131		let stake_config_account = create_config_account(vec![], &stake_config, 10);
132		assert_eq!(
133			parse_config(stake_config_account.data(), &stake_config::id()).unwrap(),
134			ConfigAccountType::StakeConfig(UiStakeConfig {
135				warmup_cooldown_rate: 0.25,
136				slash_penalty: 50,
137			}),
138		);
139
140		let validator_info = ValidatorInfo {
141			info: serde_json::to_string(&json!({
142				"name": "Solana",
143			}))
144			.unwrap(),
145		};
146		let info_pubkey = solana_pubkey::new_rand();
147		let validator_info_config_account = create_config_account(
148			vec![(validator_info::id(), false), (info_pubkey, true)],
149			&validator_info,
150			10,
151		);
152		assert_eq!(
153			parse_config(validator_info_config_account.data(), &info_pubkey).unwrap(),
154			ConfigAccountType::ValidatorInfo(UiConfig {
155				keys: vec![
156					UiConfigKey {
157						pubkey: validator_info::id().to_string(),
158						signer: false,
159					},
160					UiConfigKey {
161						pubkey: info_pubkey.to_string(),
162						signer: true,
163					}
164				],
165				config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(),
166			}),
167		);
168
169		let bad_data = vec![0; 4];
170		assert!(parse_config(&bad_data, &info_pubkey).is_err());
171	}
172}