solana_account_decoder/
parse_config.rs

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