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::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(spl_token::id(), ParsableAccount::SplToken);
37        m.insert(spl_token_2022::id(), ParsableAccount::SplToken2022);
38        m.insert(stake::id(), ParsableAccount::Stake);
39        m.insert(sysvar::id(), ParsableAccount::Sysvar);
40        m.insert(vote::id(), ParsableAccount::Vote);
41        m
42    });
43
44#[derive(Error, Debug)]
45pub enum ParseAccountError {
46    #[error("{0:?} account not parsable")]
47    AccountNotParsable(ParsableAccount),
48
49    #[error("Program not parsable")]
50    ProgramNotParsable,
51
52    #[error("Additional data required to parse: {0}")]
53    AdditionalDataMissing(String),
54
55    #[error("Instruction error")]
56    InstructionError(#[from] InstructionError),
57
58    #[error("Serde json error")]
59    SerdeJsonError(#[from] serde_json::error::Error),
60}
61
62#[derive(Debug, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub enum ParsableAccount {
65    AddressLookupTable,
66    BpfUpgradeableLoader,
67    Config,
68    Nonce,
69    SplToken,
70    SplToken2022,
71    Stake,
72    Sysvar,
73    Vote,
74}
75
76#[deprecated(since = "2.0.0", note = "Use `AccountAdditionalDataV3` instead")]
77#[derive(Clone, Copy, Default)]
78pub struct AccountAdditionalData {
79    pub spl_token_decimals: Option<u8>,
80}
81
82#[deprecated(since = "2.2.0", note = "Use `AccountAdditionalDataV3` instead")]
83#[derive(Clone, Copy, Default)]
84pub struct AccountAdditionalDataV2 {
85    pub spl_token_additional_data: Option<SplTokenAdditionalData>,
86}
87
88#[derive(Clone, Copy, Default)]
89pub struct AccountAdditionalDataV3 {
90    pub spl_token_additional_data: Option<SplTokenAdditionalDataV2>,
91}
92
93#[allow(deprecated)]
94impl From<AccountAdditionalDataV2> for AccountAdditionalDataV3 {
95    fn from(v: AccountAdditionalDataV2) -> Self {
96        Self {
97            spl_token_additional_data: v.spl_token_additional_data.map(Into::into),
98        }
99    }
100}
101
102#[derive(Clone, Copy, Default)]
103pub struct SplTokenAdditionalData {
104    pub decimals: u8,
105    pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
106}
107
108impl SplTokenAdditionalData {
109    pub fn with_decimals(decimals: u8) -> Self {
110        Self {
111            decimals,
112            ..Default::default()
113        }
114    }
115}
116
117#[derive(Clone, Copy, Default)]
118pub struct SplTokenAdditionalDataV2 {
119    pub decimals: u8,
120    pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
121    pub scaled_ui_amount_config: Option<(ScaledUiAmountConfig, UnixTimestamp)>,
122}
123
124impl From<SplTokenAdditionalData> for SplTokenAdditionalDataV2 {
125    fn from(v: SplTokenAdditionalData) -> Self {
126        Self {
127            decimals: v.decimals,
128            interest_bearing_config: v.interest_bearing_config,
129            scaled_ui_amount_config: None,
130        }
131    }
132}
133
134impl SplTokenAdditionalDataV2 {
135    pub fn with_decimals(decimals: u8) -> Self {
136        Self {
137            decimals,
138            ..Default::default()
139        }
140    }
141}
142
143#[deprecated(since = "2.0.0", note = "Use `parse_account_data_v3` instead")]
144#[allow(deprecated)]
145pub fn parse_account_data(
146    pubkey: &Pubkey,
147    program_id: &Pubkey,
148    data: &[u8],
149    additional_data: Option<AccountAdditionalData>,
150) -> Result<ParsedAccount, ParseAccountError> {
151    parse_account_data_v3(
152        pubkey,
153        program_id,
154        data,
155        additional_data.map(|d| AccountAdditionalDataV3 {
156            spl_token_additional_data: d
157                .spl_token_decimals
158                .map(SplTokenAdditionalDataV2::with_decimals),
159        }),
160    )
161}
162
163#[deprecated(since = "2.2.0", note = "Use `parse_account_data_v3` instead")]
164#[allow(deprecated)]
165pub fn parse_account_data_v2(
166    pubkey: &Pubkey,
167    program_id: &Pubkey,
168    data: &[u8],
169    additional_data: Option<AccountAdditionalDataV2>,
170) -> Result<ParsedAccount, ParseAccountError> {
171    parse_account_data_v3(pubkey, program_id, data, additional_data.map(Into::into))
172}
173
174pub fn parse_account_data_v3(
175    pubkey: &Pubkey,
176    program_id: &Pubkey,
177    data: &[u8],
178    additional_data: Option<AccountAdditionalDataV3>,
179) -> Result<ParsedAccount, ParseAccountError> {
180    let program_name = PARSABLE_PROGRAM_IDS
181        .get(program_id)
182        .ok_or(ParseAccountError::ProgramNotParsable)?;
183    let additional_data = additional_data.unwrap_or_default();
184    let parsed_json = match program_name {
185        ParsableAccount::AddressLookupTable => {
186            serde_json::to_value(parse_address_lookup_table(data)?)?
187        }
188        ParsableAccount::BpfUpgradeableLoader => {
189            serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
190        }
191        ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
192        ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
193        ParsableAccount::SplToken | ParsableAccount::SplToken2022 => serde_json::to_value(
194            parse_token_v3(data, additional_data.spl_token_additional_data.as_ref())?,
195        )?,
196        ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
197        ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
198        ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
199    };
200    Ok(ParsedAccount {
201        program: format!("{program_name:?}").to_kebab_case(),
202        parsed: parsed_json,
203        space: data.len() as u64,
204    })
205}
206
207#[cfg(test)]
208mod test {
209    use {
210        super::*,
211        solana_nonce::{
212            state::{Data, State},
213            versions::Versions,
214        },
215        solana_vote_interface::{
216            program::id as vote_program_id,
217            state::{VoteState, VoteStateVersions},
218        },
219    };
220
221    #[test]
222    fn test_parse_account_data() {
223        let account_pubkey = solana_pubkey::new_rand();
224        let other_program = solana_pubkey::new_rand();
225        let data = vec![0; 4];
226        assert!(parse_account_data_v3(&account_pubkey, &other_program, &data, None).is_err());
227
228        let vote_state = VoteState::default();
229        let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
230        let versioned = VoteStateVersions::new_current(vote_state);
231        VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
232        let parsed = parse_account_data_v3(
233            &account_pubkey,
234            &vote_program_id(),
235            &vote_account_data,
236            None,
237        )
238        .unwrap();
239        assert_eq!(parsed.program, "vote".to_string());
240        assert_eq!(parsed.space, VoteState::size_of() as u64);
241
242        let nonce_data = Versions::new(State::Initialized(Data::default()));
243        let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
244        let parsed = parse_account_data_v3(
245            &account_pubkey,
246            &system_program::id(),
247            &nonce_account_data,
248            None,
249        )
250        .unwrap();
251        assert_eq!(parsed.program, "nonce".to_string());
252        assert_eq!(parsed.space, State::size() as u64);
253    }
254}