solana_account_decoder_wasm/
parse_account_data.rs1use std::collections::HashMap;
2
3use base64::Engine;
4use base64::prelude::BASE64_STANDARD;
5use inflector::Inflector;
6use serde::Deserialize;
7use serde::Serialize;
8use solana_account::ReadableAccount;
9pub use solana_account_decoder_client_types_wasm::ParsedAccount;
10use solana_account_decoder_client_types_wasm::UiAccount;
11use solana_account_decoder_client_types_wasm::UiAccountData;
12use solana_account_decoder_client_types_wasm::UiAccountEncoding;
13use solana_account_decoder_client_types_wasm::UiDataSliceConfig;
14use solana_clock::UnixTimestamp;
15use solana_instruction::error::InstructionError;
16use solana_pubkey::Pubkey;
17use solana_sdk_ids::address_lookup_table;
18use solana_sdk_ids::bpf_loader_upgradeable;
19use solana_sdk_ids::config;
20use solana_sdk_ids::stake;
21use solana_sdk_ids::system_program;
22use solana_sdk_ids::sysvar;
23use solana_sdk_ids::vote;
24use spl_token_2022::extension::interest_bearing_mint::InterestBearingConfig;
25use spl_token_2022::extension::scaled_ui_amount::ScaledUiAmountConfig;
26use thiserror::Error;
27
28use crate::MAX_BASE58_BYTES;
29use crate::parse_address_lookup_table::parse_address_lookup_table;
30use crate::parse_bpf_loader::parse_bpf_upgradeable_loader;
31use crate::parse_config::parse_config;
32use crate::parse_nonce::parse_nonce;
33use crate::parse_stake::parse_stake;
34use crate::parse_sysvar::parse_sysvar;
35use crate::parse_token::parse_token_v3;
36use crate::parse_vote::parse_vote;
37use crate::slice_data;
38
39pub static PARSABLE_PROGRAM_IDS: std::sync::LazyLock<HashMap<Pubkey, ParsableAccount>> =
40 std::sync::LazyLock::new(|| {
41 let mut m = HashMap::new();
42 m.insert(
43 address_lookup_table::id(),
44 ParsableAccount::AddressLookupTable,
45 );
46 m.insert(
47 bpf_loader_upgradeable::id(),
48 ParsableAccount::BpfUpgradeableLoader,
49 );
50 m.insert(config::id(), ParsableAccount::Config);
51 m.insert(system_program::id(), ParsableAccount::Nonce);
52 m.insert(spl_token::id(), ParsableAccount::SplToken);
53 m.insert(spl_token_2022::id(), ParsableAccount::SplToken2022);
54 m.insert(stake::id(), ParsableAccount::Stake);
55 m.insert(sysvar::id(), ParsableAccount::Sysvar);
56 m.insert(vote::id(), ParsableAccount::Vote);
57 m
58 });
59
60#[derive(Error, Debug)]
61pub enum ParseAccountError {
62 #[error("{0:?} account not parsable")]
63 AccountNotParsable(ParsableAccount),
64
65 #[error("Program not parsable")]
66 ProgramNotParsable,
67
68 #[error("Additional data required to parse: {0}")]
69 AdditionalDataMissing(String),
70
71 #[error("Instruction error")]
72 InstructionError(#[from] InstructionError),
73
74 #[error("Serde json error")]
75 SerdeJsonError(#[from] serde_json::error::Error),
76}
77
78#[derive(Debug, Serialize, Deserialize)]
79#[serde(rename_all = "camelCase")]
80pub enum ParsableAccount {
81 AddressLookupTable,
82 BpfUpgradeableLoader,
83 Config,
84 Nonce,
85 SplToken,
86 SplToken2022,
87 Stake,
88 Sysvar,
89 Vote,
90}
91
92#[deprecated(since = "2.0.0", note = "Use `AccountAdditionalDataV3` instead")]
93#[derive(Clone, Copy, Default)]
94pub struct AccountAdditionalData {
95 pub spl_token_decimals: Option<u8>,
96}
97
98#[deprecated(since = "2.2.0", note = "Use `AccountAdditionalDataV3` instead")]
99#[derive(Clone, Copy, Default)]
100pub struct AccountAdditionalDataV2 {
101 pub spl_token_additional_data: Option<SplTokenAdditionalData>,
102}
103
104pub trait EncodeUiAccount {}
105
106fn encode_ui_accout_bs58<T: ReadableAccount>(
107 account: &T,
108 data_slice_config: Option<UiDataSliceConfig>,
109) -> String {
110 let slice = slice_data(account.data(), data_slice_config);
111 if slice.len() <= MAX_BASE58_BYTES {
112 bs58::encode(slice).into_string()
113 } else {
114 "error: data too large for bs58 encoding".to_string()
115 }
116}
117
118pub fn encode_ui_account<T: ReadableAccount>(
119 pubkey: &Pubkey,
120 account: &T,
121 encoding: UiAccountEncoding,
122 additional_data: Option<AccountAdditionalDataV2>,
123 data_slice_config: Option<UiDataSliceConfig>,
124) -> UiAccount {
125 let space = account.data().len();
126 let data = match encoding {
127 UiAccountEncoding::Binary => {
128 let data = encode_ui_accout_bs58(account, data_slice_config);
129 UiAccountData::LegacyBinary(data)
130 }
131 UiAccountEncoding::Base58 => {
132 let data = encode_ui_accout_bs58(account, data_slice_config);
133 UiAccountData::Binary(data, encoding)
134 }
135 UiAccountEncoding::Base64 => {
136 UiAccountData::Binary(
137 BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)),
138 encoding,
139 )
140 }
141 #[cfg(not(feature = "zstd"))]
142 UiAccountEncoding::Base64Zstd => todo!("zstd not supported without the zstd feature flag"),
143 #[cfg(feature = "zstd")]
144 UiAccountEncoding::Base64Zstd => {
145 use std::io::Write;
146
147 let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
148 match encoder
149 .write_all(slice_data(account.data(), data_slice_config))
150 .and_then(|()| encoder.finish())
151 {
152 Ok(zstd_data) => UiAccountData::Binary(BASE64_STANDARD.encode(zstd_data), encoding),
153 Err(_) => {
154 UiAccountData::Binary(
155 BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)),
156 UiAccountEncoding::Base64,
157 )
158 }
159 }
160 }
161 UiAccountEncoding::JsonParsed => {
162 if let Ok(parsed_data) =
163 parse_account_data_v2(pubkey, account.owner(), account.data(), additional_data)
164 {
165 UiAccountData::Json(parsed_data)
166 } else {
167 UiAccountData::Binary(
168 BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)),
169 UiAccountEncoding::Base64,
170 )
171 }
172 }
173 };
174 UiAccount {
175 lamports: account.lamports(),
176 data,
177 owner: *account.owner(),
178 executable: account.executable(),
179 rent_epoch: account.rent_epoch(),
180 space: Some(space as u64),
181 }
182}
183
184#[derive(Clone, Copy, Default)]
185pub struct AccountAdditionalDataV3 {
186 pub spl_token_additional_data: Option<SplTokenAdditionalDataV2>,
187}
188
189#[allow(deprecated)]
190impl From<AccountAdditionalDataV2> for AccountAdditionalDataV3 {
191 fn from(v: AccountAdditionalDataV2) -> Self {
192 Self {
193 spl_token_additional_data: v.spl_token_additional_data.map(Into::into),
194 }
195 }
196}
197
198#[derive(Clone, Copy, Default)]
199pub struct SplTokenAdditionalData {
200 pub decimals: u8,
201 pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
202}
203
204impl SplTokenAdditionalData {
205 pub fn with_decimals(decimals: u8) -> Self {
206 Self {
207 decimals,
208 ..Default::default()
209 }
210 }
211}
212
213#[derive(Clone, Copy, Default)]
214pub struct SplTokenAdditionalDataV2 {
215 pub decimals: u8,
216 pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
217 pub scaled_ui_amount_config: Option<(ScaledUiAmountConfig, UnixTimestamp)>,
218}
219
220impl From<SplTokenAdditionalData> for SplTokenAdditionalDataV2 {
221 fn from(v: SplTokenAdditionalData) -> Self {
222 Self {
223 decimals: v.decimals,
224 interest_bearing_config: v.interest_bearing_config,
225 scaled_ui_amount_config: None,
226 }
227 }
228}
229
230impl SplTokenAdditionalDataV2 {
231 pub fn with_decimals(decimals: u8) -> Self {
232 Self {
233 decimals,
234 ..Default::default()
235 }
236 }
237}
238
239#[deprecated(since = "2.0.0", note = "Use `parse_account_data_v3` instead")]
240#[allow(deprecated)]
241pub fn parse_account_data(
242 pubkey: &Pubkey,
243 program_id: &Pubkey,
244 data: &[u8],
245 additional_data: Option<AccountAdditionalData>,
246) -> Result<ParsedAccount, ParseAccountError> {
247 parse_account_data_v3(
248 pubkey,
249 program_id,
250 data,
251 additional_data.map(|d| {
252 AccountAdditionalDataV3 {
253 spl_token_additional_data: d
254 .spl_token_decimals
255 .map(SplTokenAdditionalDataV2::with_decimals),
256 }
257 }),
258 )
259}
260
261#[deprecated(since = "2.2.0", note = "Use `parse_account_data_v3` instead")]
262#[allow(deprecated)]
263pub fn parse_account_data_v2(
264 pubkey: &Pubkey,
265 program_id: &Pubkey,
266 data: &[u8],
267 additional_data: Option<AccountAdditionalDataV2>,
268) -> Result<ParsedAccount, ParseAccountError> {
269 parse_account_data_v3(pubkey, program_id, data, additional_data.map(Into::into))
270}
271
272pub fn parse_account_data_v3(
273 pubkey: &Pubkey,
274 program_id: &Pubkey,
275 data: &[u8],
276 additional_data: Option<AccountAdditionalDataV3>,
277) -> Result<ParsedAccount, ParseAccountError> {
278 let program_name = PARSABLE_PROGRAM_IDS
279 .get(program_id)
280 .ok_or(ParseAccountError::ProgramNotParsable)?;
281 let additional_data = additional_data.unwrap_or_default();
282 let parsed_json = match program_name {
283 ParsableAccount::AddressLookupTable => {
284 serde_json::to_value(parse_address_lookup_table(data)?)?
285 }
286 ParsableAccount::BpfUpgradeableLoader => {
287 serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
288 }
289 ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
290 ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
291 ParsableAccount::SplToken | ParsableAccount::SplToken2022 => {
292 serde_json::to_value(parse_token_v3(
293 data,
294 additional_data.spl_token_additional_data.as_ref(),
295 )?)?
296 }
297 ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
298 ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
299 ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
300 };
301 Ok(ParsedAccount {
302 program: format!("{program_name:?}").to_kebab_case(),
303 parsed: parsed_json,
304 space: data.len() as u64,
305 })
306}
307
308#[cfg(test)]
309mod test {
310 use solana_nonce::state::Data;
311 use solana_nonce::state::State;
312 use solana_nonce::versions::Versions;
313 use solana_vote_interface::program::id as vote_program_id;
314 use solana_vote_interface::state::VoteState;
315 use solana_vote_interface::state::VoteStateVersions;
316
317 use super::*;
318
319 #[test]
320 fn test_parse_account_data() {
321 let account_pubkey = solana_pubkey::new_rand();
322 let other_program = solana_pubkey::new_rand();
323 let data = vec![0; 4];
324 assert!(parse_account_data_v3(&account_pubkey, &other_program, &data, None).is_err());
325
326 let vote_state = VoteState::default();
327 let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
328 let versioned = VoteStateVersions::new_current(vote_state);
329 VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
330 let parsed = parse_account_data_v3(
331 &account_pubkey,
332 &vote_program_id(),
333 &vote_account_data,
334 None,
335 )
336 .unwrap();
337 assert_eq!(parsed.program, "vote".to_string());
338 assert_eq!(parsed.space, VoteState::size_of() as u64);
339
340 let nonce_data = Versions::new(State::Initialized(Data::default()));
341 let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
342 let parsed = parse_account_data_v3(
343 &account_pubkey,
344 &system_program::id(),
345 &nonce_account_data,
346 None,
347 )
348 .unwrap();
349 assert_eq!(parsed.program, "nonce".to_string());
350 assert_eq!(parsed.space, State::size() as u64);
351 }
352}