solana_account_decoder_wasm/
parse_config.rs1use 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}