solana_account_decoder/
parse_config.rs

1use {
2    crate::{
3        parse_account_data::{ParsableAccount, ParseAccountError},
4        validator_info,
5    },
6    bincode::deserialize,
7    serde_json::Value,
8    solana_config_program::{get_config_data, ConfigKeys},
9    solana_program::stake::config::{
10        Config as StakeConfig, {self as stake_config},
11    },
12    solana_pubkey::Pubkey,
13};
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| UiConfigKey {
48            pubkey: key.0.to_string(),
49            signer: key.1,
50        })
51        .collect();
52    Some(UiConfig { keys, config_data })
53}
54
55#[derive(Debug, Serialize, Deserialize, PartialEq)]
56#[serde(rename_all = "camelCase", tag = "type", content = "info")]
57pub enum ConfigAccountType {
58    StakeConfig(UiStakeConfig),
59    ValidatorInfo(UiConfig<Value>),
60}
61
62#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(rename_all = "camelCase")]
64pub struct UiConfigKey {
65    pub pubkey: String,
66    pub signer: bool,
67}
68
69#[deprecated(
70    since = "1.16.7",
71    note = "Please use `solana_program::stake::state::warmup_cooldown_rate()` instead"
72)]
73#[derive(Debug, Serialize, Deserialize, PartialEq)]
74#[serde(rename_all = "camelCase")]
75pub struct UiStakeConfig {
76    pub warmup_cooldown_rate: f64,
77    pub slash_penalty: u8,
78}
79
80impl From<StakeConfig> for UiStakeConfig {
81    fn from(config: StakeConfig) -> Self {
82        Self {
83            warmup_cooldown_rate: config.warmup_cooldown_rate,
84            slash_penalty: config.slash_penalty,
85        }
86    }
87}
88
89#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
90#[serde(rename_all = "camelCase")]
91pub struct UiConfig<T> {
92    pub keys: Vec<UiConfigKey>,
93    pub config_data: T,
94}
95
96#[cfg(test)]
97mod test {
98    use {
99        super::*, crate::validator_info::ValidatorInfo, serde_json::json,
100        solana_account::ReadableAccount, solana_config_program::create_config_account,
101    };
102
103    #[test]
104    fn test_parse_config() {
105        let stake_config = StakeConfig {
106            warmup_cooldown_rate: 0.25,
107            slash_penalty: 50,
108        };
109        let stake_config_account = create_config_account(vec![], &stake_config, 10);
110        assert_eq!(
111            parse_config(stake_config_account.data(), &stake_config::id()).unwrap(),
112            ConfigAccountType::StakeConfig(UiStakeConfig {
113                warmup_cooldown_rate: 0.25,
114                slash_penalty: 50,
115            }),
116        );
117
118        let validator_info = ValidatorInfo {
119            info: serde_json::to_string(&json!({
120                "name": "Solana",
121            }))
122            .unwrap(),
123        };
124        let info_pubkey = solana_pubkey::new_rand();
125        let validator_info_config_account = create_config_account(
126            vec![(validator_info::id(), false), (info_pubkey, true)],
127            &validator_info,
128            10,
129        );
130        assert_eq!(
131            parse_config(validator_info_config_account.data(), &info_pubkey).unwrap(),
132            ConfigAccountType::ValidatorInfo(UiConfig {
133                keys: vec![
134                    UiConfigKey {
135                        pubkey: validator_info::id().to_string(),
136                        signer: false,
137                    },
138                    UiConfigKey {
139                        pubkey: info_pubkey.to_string(),
140                        signer: true,
141                    }
142                ],
143                config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(),
144            }),
145        );
146
147        let bad_data = vec![0; 4];
148        assert!(parse_config(&bad_data, &info_pubkey).is_err());
149    }
150}