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    serde::{Deserialize, Serialize},
11    solana_clock::UnixTimestamp,
12    solana_instruction::error::InstructionError,
13    solana_pubkey::Pubkey,
14    solana_sdk_ids::{
15        address_lookup_table, bpf_loader_upgradeable, config, stake, system_program, sysvar, vote,
16    },
17    spl_token_2022_interface::extension::{
18        interest_bearing_mint::InterestBearingConfig, scaled_ui_amount::ScaledUiAmountConfig,
19    },
20    std::collections::HashMap,
21    thiserror::Error,
22};
23
24pub static PARSABLE_PROGRAM_IDS: std::sync::LazyLock<HashMap<Pubkey, ParsableAccount>> =
25    std::sync::LazyLock::new(|| {
26        let mut m = HashMap::new();
27        m.insert(
28            address_lookup_table::id(),
29            ParsableAccount::AddressLookupTable,
30        );
31        m.insert(
32            bpf_loader_upgradeable::id(),
33            ParsableAccount::BpfUpgradeableLoader,
34        );
35        m.insert(config::id(), ParsableAccount::Config);
36        m.insert(system_program::id(), ParsableAccount::Nonce);
37        m.insert(
38            spl_token_2022_interface::id(),
39            ParsableAccount::SplToken2022,
40        );
41        m.insert(spl_token_interface::id(), ParsableAccount::SplToken);
42        m.insert(stake::id(), ParsableAccount::Stake);
43        m.insert(sysvar::id(), ParsableAccount::Sysvar);
44        m.insert(vote::id(), ParsableAccount::Vote);
45        m
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#[derive(Clone, Copy, Default)]
81pub struct AccountAdditionalDataV3 {
82    pub spl_token_additional_data: Option<SplTokenAdditionalDataV2>,
83}
84
85#[derive(Clone, Copy, Default)]
86pub struct SplTokenAdditionalData {
87    pub decimals: u8,
88    pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
89}
90
91impl SplTokenAdditionalData {
92    pub fn with_decimals(decimals: u8) -> Self {
93        Self {
94            decimals,
95            ..Default::default()
96        }
97    }
98}
99
100#[derive(Clone, Copy, Default)]
101pub struct SplTokenAdditionalDataV2 {
102    pub decimals: u8,
103    pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
104    pub scaled_ui_amount_config: Option<(ScaledUiAmountConfig, UnixTimestamp)>,
105}
106
107impl From<SplTokenAdditionalData> for SplTokenAdditionalDataV2 {
108    fn from(v: SplTokenAdditionalData) -> Self {
109        Self {
110            decimals: v.decimals,
111            interest_bearing_config: v.interest_bearing_config,
112            scaled_ui_amount_config: None,
113        }
114    }
115}
116
117impl SplTokenAdditionalDataV2 {
118    pub fn with_decimals(decimals: u8) -> Self {
119        Self {
120            decimals,
121            ..Default::default()
122        }
123    }
124}
125
126pub fn parse_account_data_v3(
127    pubkey: &Pubkey,
128    program_id: &Pubkey,
129    data: &[u8],
130    additional_data: Option<AccountAdditionalDataV3>,
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_v3(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, pubkey)?)?,
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_nonce::{
164            state::{Data, State},
165            versions::Versions,
166        },
167        solana_vote_interface::{
168            program::id as vote_program_id,
169            state::{VoteStateV4, VoteStateVersions},
170        },
171    };
172
173    #[test]
174    fn test_parse_account_data() {
175        let account_pubkey = solana_pubkey::new_rand();
176        let other_program = solana_pubkey::new_rand();
177        let data = vec![0; 4];
178        assert!(parse_account_data_v3(&account_pubkey, &other_program, &data, None).is_err());
179
180        let vote_state = VoteStateV4::default();
181        let mut vote_account_data: Vec<u8> = vec![0; VoteStateV4::size_of()];
182        let versioned = VoteStateVersions::new_v4(vote_state);
183        VoteStateV4::serialize(&versioned, &mut vote_account_data).unwrap();
184        let parsed = parse_account_data_v3(
185            &account_pubkey,
186            &vote_program_id(),
187            &vote_account_data,
188            None,
189        )
190        .unwrap();
191        assert_eq!(parsed.program, "vote".to_string());
192        assert_eq!(parsed.space, VoteStateV4::size_of() as u64);
193
194        let nonce_data = Versions::new(State::Initialized(Data::default()));
195        let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
196        let parsed = parse_account_data_v3(
197            &account_pubkey,
198            &system_program::id(),
199            &nonce_account_data,
200            None,
201        )
202        .unwrap();
203        assert_eq!(parsed.program, "nonce".to_string());
204        assert_eq!(parsed.space, State::size() as u64);
205    }
206}