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_sdk::{
10        pubkey::Pubkey,
11        stake::config::{
12            Config as StakeConfig, {self as stake_config},
13        },
14    },
15};
16
17pub fn parse_config(data: &[u8], pubkey: &Pubkey) -> Result<ConfigAccountType, ParseAccountError> {
18    let parsed_account = if pubkey == &stake_config::id() {
19        get_config_data(data)
20            .ok()
21            .and_then(|data| deserialize::<StakeConfig>(data).ok())
22            .map(|config| ConfigAccountType::StakeConfig(config.into()))
23    } else {
24        deserialize::<ConfigKeys>(data).ok().and_then(|key_list| {
25            if !key_list.keys.is_empty() && key_list.keys[0].0 == validator_info::id() {
26                parse_config_data::<String>(data, key_list.keys).and_then(|validator_info| {
27                    Some(ConfigAccountType::ValidatorInfo(UiConfig {
28                        keys: validator_info.keys,
29                        config_data: serde_json::from_str(&validator_info.config_data).ok()?,
30                    }))
31                })
32            } else {
33                None
34            }
35        })
36    };
37    parsed_account.ok_or(ParseAccountError::AccountNotParsable(
38        ParsableAccount::Config,
39    ))
40}
41
42fn parse_config_data<T>(data: &[u8], keys: Vec<(Pubkey, bool)>) -> Option<UiConfig<T>>
43where
44    T: serde::de::DeserializeOwned,
45{
46    let config_data: T = deserialize(get_config_data(data).ok()?).ok()?;
47    let keys = keys
48        .iter()
49        .map(|key| UiConfigKey {
50            pubkey: key.0.to_string(),
51            signer: key.1,
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_sdk::stake::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 {
101        super::*, crate::validator_info::ValidatorInfo, serde_json::json,
102        solana_config_program::create_config_account, solana_sdk::account::ReadableAccount,
103    };
104
105    #[test]
106    fn test_parse_config() {
107        let stake_config = StakeConfig {
108            warmup_cooldown_rate: 0.25,
109            slash_penalty: 50,
110        };
111        let stake_config_account = create_config_account(vec![], &stake_config, 10);
112        assert_eq!(
113            parse_config(stake_config_account.data(), &stake_config::id()).unwrap(),
114            ConfigAccountType::StakeConfig(UiStakeConfig {
115                warmup_cooldown_rate: 0.25,
116                slash_penalty: 50,
117            }),
118        );
119
120        let validator_info = ValidatorInfo {
121            info: serde_json::to_string(&json!({
122                "name": "Solana",
123            }))
124            .unwrap(),
125        };
126        let info_pubkey = solana_sdk::pubkey::new_rand();
127        let validator_info_config_account = create_config_account(
128            vec![(validator_info::id(), false), (info_pubkey, true)],
129            &validator_info,
130            10,
131        );
132        assert_eq!(
133            parse_config(validator_info_config_account.data(), &info_pubkey).unwrap(),
134            ConfigAccountType::ValidatorInfo(UiConfig {
135                keys: vec![
136                    UiConfigKey {
137                        pubkey: validator_info::id().to_string(),
138                        signer: false,
139                    },
140                    UiConfigKey {
141                        pubkey: info_pubkey.to_string(),
142                        signer: true,
143                    }
144                ],
145                config_data: serde_json::from_str(r#"{"name":"Solana"}"#).unwrap(),
146            }),
147        );
148
149        let bad_data = vec![0; 4];
150        assert!(parse_config(&bad_data, &info_pubkey).is_err());
151    }
152}