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}