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