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