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}