solana_account_decoder/
parse_account_data.rs

1pub use solana_account_decoder_client_types::ParsedAccount;
2use {
3    crate::{
4        parse_address_lookup_table::parse_address_lookup_table,
5        parse_bpf_loader::parse_bpf_upgradeable_loader, parse_config::parse_config,
6        parse_nonce::parse_nonce, parse_stake::parse_stake, parse_sysvar::parse_sysvar,
7        parse_token::parse_token_v3, parse_vote::parse_vote,
8    },
9    inflector::Inflector,
10    solana_clock::UnixTimestamp,
11    solana_instruction::error::InstructionError,
12    solana_pubkey::Pubkey,
13    solana_sdk_ids::{
14        address_lookup_table, bpf_loader_upgradeable, config, stake, system_program, sysvar, vote,
15    },
16    spl_token_2022_interface::extension::{
17        interest_bearing_mint::InterestBearingConfig, scaled_ui_amount::ScaledUiAmountConfig,
18    },
19    std::collections::HashMap,
20    thiserror::Error,
21};
22
23pub static PARSABLE_PROGRAM_IDS: std::sync::LazyLock<HashMap<Pubkey, ParsableAccount>> =
24    std::sync::LazyLock::new(|| {
25        let mut m = HashMap::new();
26        m.insert(
27            address_lookup_table::id(),
28            ParsableAccount::AddressLookupTable,
29        );
30        m.insert(
31            bpf_loader_upgradeable::id(),
32            ParsableAccount::BpfUpgradeableLoader,
33        );
34        m.insert(config::id(), ParsableAccount::Config);
35        m.insert(system_program::id(), ParsableAccount::Nonce);
36        m.insert(
37            spl_token_2022_interface::id(),
38            ParsableAccount::SplToken2022,
39        );
40        m.insert(spl_token_interface::id(), ParsableAccount::SplToken);
41        m.insert(stake::id(), ParsableAccount::Stake);
42        m.insert(sysvar::id(), ParsableAccount::Sysvar);
43        m.insert(vote::id(), ParsableAccount::Vote);
44        m
45    });
46
47#[derive(Error, Debug)]
48pub enum ParseAccountError {
49    #[error("{0:?} account not parsable")]
50    AccountNotParsable(ParsableAccount),
51
52    #[error("Program not parsable")]
53    ProgramNotParsable,
54
55    #[error("Additional data required to parse: {0}")]
56    AdditionalDataMissing(String),
57
58    #[error("Instruction error")]
59    InstructionError(#[from] InstructionError),
60
61    #[error("Serde json error")]
62    SerdeJsonError(#[from] serde_json::error::Error),
63}
64
65#[derive(Debug, Serialize, Deserialize)]
66#[serde(rename_all = "camelCase")]
67pub enum ParsableAccount {
68    AddressLookupTable,
69    BpfUpgradeableLoader,
70    Config,
71    Nonce,
72    SplToken,
73    SplToken2022,
74    Stake,
75    Sysvar,
76    Vote,
77}
78
79#[derive(Clone, Copy, Default)]
80pub struct AccountAdditionalDataV3 {
81    pub spl_token_additional_data: Option<SplTokenAdditionalDataV2>,
82}
83
84#[derive(Clone, Copy, Default)]
85pub struct SplTokenAdditionalData {
86    pub decimals: u8,
87    pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
88}
89
90impl SplTokenAdditionalData {
91    pub fn with_decimals(decimals: u8) -> Self {
92        Self {
93            decimals,
94            ..Default::default()
95        }
96    }
97}
98
99#[derive(Clone, Copy, Default)]
100pub struct SplTokenAdditionalDataV2 {
101    pub decimals: u8,
102    pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
103    pub scaled_ui_amount_config: Option<(ScaledUiAmountConfig, UnixTimestamp)>,
104}
105
106impl From<SplTokenAdditionalData> for SplTokenAdditionalDataV2 {
107    fn from(v: SplTokenAdditionalData) -> Self {
108        Self {
109            decimals: v.decimals,
110            interest_bearing_config: v.interest_bearing_config,
111            scaled_ui_amount_config: None,
112        }
113    }
114}
115
116impl SplTokenAdditionalDataV2 {
117    pub fn with_decimals(decimals: u8) -> Self {
118        Self {
119            decimals,
120            ..Default::default()
121        }
122    }
123}
124
125pub fn parse_account_data_v3(
126    pubkey: &Pubkey,
127    program_id: &Pubkey,
128    data: &[u8],
129    additional_data: Option<AccountAdditionalDataV3>,
130) -> Result<ParsedAccount, ParseAccountError> {
131    let program_name = PARSABLE_PROGRAM_IDS
132        .get(program_id)
133        .ok_or(ParseAccountError::ProgramNotParsable)?;
134    let additional_data = additional_data.unwrap_or_default();
135    let parsed_json = match program_name {
136        ParsableAccount::AddressLookupTable => {
137            serde_json::to_value(parse_address_lookup_table(data)?)?
138        }
139        ParsableAccount::BpfUpgradeableLoader => {
140            serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
141        }
142        ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
143        ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
144        ParsableAccount::SplToken | ParsableAccount::SplToken2022 => serde_json::to_value(
145            parse_token_v3(data, additional_data.spl_token_additional_data.as_ref())?,
146        )?,
147        ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
148        ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
149        ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
150    };
151    Ok(ParsedAccount {
152        program: format!("{program_name:?}").to_kebab_case(),
153        parsed: parsed_json,
154        space: data.len() as u64,
155    })
156}
157
158#[cfg(test)]
159mod test {
160    use {
161        super::*,
162        solana_nonce::{
163            state::{Data, State},
164            versions::Versions,
165        },
166        solana_vote_interface::{
167            program::id as vote_program_id,
168            state::{VoteStateV3, VoteStateVersions},
169        },
170    };
171
172    #[test]
173    fn test_parse_account_data() {
174        let account_pubkey = solana_pubkey::new_rand();
175        let other_program = solana_pubkey::new_rand();
176        let data = vec![0; 4];
177        assert!(parse_account_data_v3(&account_pubkey, &other_program, &data, None).is_err());
178
179        let vote_state = VoteStateV3::default();
180        let mut vote_account_data: Vec<u8> = vec![0; VoteStateV3::size_of()];
181        let versioned = VoteStateVersions::new_v3(vote_state);
182        VoteStateV3::serialize(&versioned, &mut vote_account_data).unwrap();
183        let parsed = parse_account_data_v3(
184            &account_pubkey,
185            &vote_program_id(),
186            &vote_account_data,
187            None,
188        )
189        .unwrap();
190        assert_eq!(parsed.program, "vote".to_string());
191        assert_eq!(parsed.space, VoteStateV3::size_of() as u64);
192
193        let nonce_data = Versions::new(State::Initialized(Data::default()));
194        let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
195        let parsed = parse_account_data_v3(
196            &account_pubkey,
197            &system_program::id(),
198            &nonce_account_data,
199            None,
200        )
201        .unwrap();
202        assert_eq!(parsed.program, "nonce".to_string());
203        assert_eq!(parsed.space, State::size() as u64);
204    }
205}