solana_account_decoder_wasm/
parse_account_data.rs

1use 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}