wasm_client_solana/solana_account_decoder/
parse_account_data.rs

1use std::collections::HashMap;
2
3use heck::ToKebabCase;
4use lazy_static::lazy_static;
5use serde::Deserialize;
6use serde::Serialize;
7use serde_json::Value;
8use solana_sdk::address_lookup_table;
9use solana_sdk::clock::UnixTimestamp;
10use solana_sdk::instruction::InstructionError;
11use solana_sdk::pubkey::Pubkey;
12use solana_sdk::stake;
13use solana_sdk::system_program;
14use solana_sdk::sysvar;
15use solana_sdk::vote;
16use spl_token_2022::extension::interest_bearing_mint::InterestBearingConfig;
17use thiserror::Error;
18
19use super::parse_address_lookup_table::parse_address_lookup_table;
20use super::parse_bpf_loader::parse_bpf_upgradeable_loader;
21use super::parse_config::parse_config;
22use super::parse_nonce::parse_nonce;
23use super::parse_stake::parse_stake;
24use super::parse_sysvar::parse_sysvar;
25#[allow(deprecated)]
26use super::parse_token::parse_token_v2;
27use super::parse_vote::parse_vote;
28
29lazy_static! {
30	static ref ADDRESS_LOOKUP_PROGRAM_ID: Pubkey = address_lookup_table::program::id();
31	static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id();
32	static ref CONFIG_PROGRAM_ID: Pubkey = solana_program::config::program::id();
33	static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id();
34	static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
35	static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
36	static ref VOTE_PROGRAM_ID: Pubkey = vote::program::id();
37	pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
38		let mut m = HashMap::new();
39		m.insert(
40			*ADDRESS_LOOKUP_PROGRAM_ID,
41			ParsableAccount::AddressLookupTable,
42		);
43		m.insert(
44			*BPF_UPGRADEABLE_LOADER_PROGRAM_ID,
45			ParsableAccount::BpfUpgradeableLoader,
46		);
47		m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
48		m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
49		m.insert(spl_token::id(), ParsableAccount::SplToken);
50		m.insert(spl_token_2022::id(), ParsableAccount::SplToken2022);
51		m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
52		m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
53		m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
54		m
55	};
56}
57
58#[derive(Error, Debug)]
59pub enum ParseAccountError {
60	#[error("{0:?} account not parsable")]
61	AccountNotParsable(ParsableAccount),
62
63	#[error("Program not parsable")]
64	ProgramNotParsable,
65
66	#[error("Additional data required to parse: {0}")]
67	AdditionalDataMissing(String),
68
69	#[error("Instruction error")]
70	InstructionError(#[from] InstructionError),
71
72	#[error("Serde json error")]
73	SerdeJsonError(#[from] serde_json::error::Error),
74}
75
76#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
77#[serde(rename_all = "camelCase")]
78pub struct ParsedAccount {
79	pub program: String,
80	pub parsed: Value,
81	pub space: u64,
82}
83
84#[derive(Debug, Serialize, Deserialize)]
85#[serde(rename_all = "camelCase")]
86pub enum ParsableAccount {
87	AddressLookupTable,
88	BpfUpgradeableLoader,
89	Config,
90	Nonce,
91	SplToken,
92	SplToken2022,
93	Stake,
94	Sysvar,
95	Vote,
96}
97
98#[deprecated(since = "2.0.0", note = "Use `AccountAdditionalDataV2` instead")]
99#[derive(Clone, Copy, Default)]
100pub struct AccountAdditionalData {
101	pub spl_token_decimals: Option<u8>,
102}
103
104#[derive(Clone, Copy, Default)]
105pub struct AccountAdditionalDataV2 {
106	pub spl_token_additional_data: Option<SplTokenAdditionalData>,
107}
108
109#[derive(Clone, Copy, Default)]
110pub struct SplTokenAdditionalData {
111	pub decimals: u8,
112	pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
113}
114
115impl SplTokenAdditionalData {
116	pub fn with_decimals(decimals: u8) -> Self {
117		Self {
118			decimals,
119			..Default::default()
120		}
121	}
122}
123
124#[deprecated(since = "2.0.0", note = "Use `parse_account_data_v2` instead")]
125#[allow(deprecated)]
126pub fn parse_account_data(
127	pubkey: &Pubkey,
128	program_id: &Pubkey,
129	data: &[u8],
130	additional_data: Option<AccountAdditionalData>,
131) -> Result<ParsedAccount, ParseAccountError> {
132	parse_account_data_v2(
133		pubkey,
134		program_id,
135		data,
136		additional_data.map(|d| {
137			AccountAdditionalDataV2 {
138				spl_token_additional_data: d
139					.spl_token_decimals
140					.map(SplTokenAdditionalData::with_decimals),
141			}
142		}),
143	)
144}
145
146#[allow(deprecated)]
147pub fn parse_account_data_v2(
148	pubkey: &Pubkey,
149	program_id: &Pubkey,
150	data: &[u8],
151	additional_data: Option<AccountAdditionalDataV2>,
152) -> Result<ParsedAccount, ParseAccountError> {
153	let program_name = PARSABLE_PROGRAM_IDS
154		.get(program_id)
155		.ok_or(ParseAccountError::ProgramNotParsable)?;
156	let additional_data = additional_data.unwrap_or_default();
157	let parsed_json = match program_name {
158		ParsableAccount::AddressLookupTable => {
159			serde_json::to_value(parse_address_lookup_table(data)?)?
160		}
161		ParsableAccount::BpfUpgradeableLoader => {
162			serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
163		}
164		ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
165		ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
166		ParsableAccount::SplToken | ParsableAccount::SplToken2022 => {
167			serde_json::to_value(parse_token_v2(
168				data,
169				additional_data.spl_token_additional_data.as_ref(),
170			)?)?
171		}
172		ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
173		ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
174		ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
175	};
176	Ok(ParsedAccount {
177		program: format!("{program_name:?}").to_kebab_case(),
178		parsed: parsed_json,
179		space: data.len() as u64,
180	})
181}
182
183#[cfg(test)]
184mod test {
185	use solana_sdk::nonce::State;
186	use solana_sdk::nonce::state::Data;
187	use solana_sdk::nonce::state::Versions;
188	use solana_sdk::vote::program::id as vote_program_id;
189	use solana_sdk::vote::state::VoteState;
190	use solana_sdk::vote::state::VoteStateVersions;
191
192	use super::*;
193
194	#[test]
195	fn test_parse_account_data() {
196		let account_pubkey = solana_sdk::pubkey::new_rand();
197		let other_program = solana_sdk::pubkey::new_rand();
198		let data = vec![0; 4];
199		assert!(parse_account_data_v2(&account_pubkey, &other_program, &data, None).is_err());
200
201		let vote_state = VoteState::default();
202		let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
203		let versioned = VoteStateVersions::new_current(vote_state);
204		VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
205		let parsed = parse_account_data_v2(
206			&account_pubkey,
207			&vote_program_id(),
208			&vote_account_data,
209			None,
210		)
211		.unwrap();
212		assert_eq!(parsed.program, "vote".to_string());
213		assert_eq!(parsed.space, VoteState::size_of() as u64);
214
215		let nonce_data = Versions::new(State::Initialized(Data::default()));
216		let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
217		let parsed = parse_account_data_v2(
218			&account_pubkey,
219			&system_program::id(),
220			&nonce_account_data,
221			None,
222		)
223		.unwrap();
224		assert_eq!(parsed.program, "nonce".to_string());
225		assert_eq!(parsed.space, State::size() as u64);
226	}
227}