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_interface::extension::interest_bearing_mint::InterestBearingConfig;
25use spl_token_2022_interface::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_interface::id(), ParsableAccount::SplToken);
53 m.insert(
54 spl_token_2022_interface::id(),
55 ParsableAccount::SplToken2022,
56 );
57 m.insert(stake::id(), ParsableAccount::Stake);
58 m.insert(sysvar::id(), ParsableAccount::Sysvar);
59 m.insert(vote::id(), ParsableAccount::Vote);
60 m
61 });
62
63#[derive(Error, Debug)]
64pub enum ParseAccountError {
65 #[error("{0:?} account not parsable")]
66 AccountNotParsable(ParsableAccount),
67
68 #[error("Program not parsable")]
69 ProgramNotParsable,
70
71 #[error("Additional data required to parse: {0}")]
72 AdditionalDataMissing(String),
73
74 #[error("Instruction error")]
75 InstructionError(#[from] InstructionError),
76
77 #[error("Serde json error")]
78 SerdeJsonError(#[from] serde_json::error::Error),
79}
80
81#[derive(Debug, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub enum ParsableAccount {
84 AddressLookupTable,
85 BpfUpgradeableLoader,
86 Config,
87 Nonce,
88 SplToken,
89 SplToken2022,
90 Stake,
91 Sysvar,
92 Vote,
93}
94
95pub trait EncodeUiAccount {}
96
97fn encode_ui_accout_bs58<T: ReadableAccount>(
98 account: &T,
99 data_slice_config: Option<UiDataSliceConfig>,
100) -> String {
101 let slice = slice_data(account.data(), data_slice_config);
102 if slice.len() <= MAX_BASE58_BYTES {
103 bs58::encode(slice).into_string()
104 } else {
105 "error: data too large for bs58 encoding".to_string()
106 }
107}
108
109pub fn encode_ui_account<T: ReadableAccount>(
110 pubkey: &Pubkey,
111 account: &T,
112 encoding: UiAccountEncoding,
113 additional_data: Option<AccountAdditionalDataV3>,
114 data_slice_config: Option<UiDataSliceConfig>,
115) -> UiAccount {
116 let space = account.data().len();
117 let data = match encoding {
118 UiAccountEncoding::Binary => {
119 let data = encode_ui_accout_bs58(account, data_slice_config);
120 UiAccountData::LegacyBinary(data)
121 }
122 UiAccountEncoding::Base58 => {
123 let data = encode_ui_accout_bs58(account, data_slice_config);
124 UiAccountData::Binary(data, encoding)
125 }
126 UiAccountEncoding::Base64 => {
127 UiAccountData::Binary(
128 BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)),
129 encoding,
130 )
131 }
132 #[cfg(not(feature = "zstd"))]
133 UiAccountEncoding::Base64Zstd => todo!("zstd not supported without the zstd feature flag"),
134 #[cfg(feature = "zstd")]
135 UiAccountEncoding::Base64Zstd => {
136 use std::io::Write;
137
138 let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
139 match encoder
140 .write_all(slice_data(account.data(), data_slice_config))
141 .and_then(|()| encoder.finish())
142 {
143 Ok(zstd_data) => UiAccountData::Binary(BASE64_STANDARD.encode(zstd_data), encoding),
144 Err(_) => {
145 UiAccountData::Binary(
146 BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)),
147 UiAccountEncoding::Base64,
148 )
149 }
150 }
151 }
152 UiAccountEncoding::JsonParsed => {
153 if let Ok(parsed_data) =
154 parse_account_data_v3(pubkey, account.owner(), account.data(), additional_data)
155 {
156 UiAccountData::Json(parsed_data)
157 } else {
158 UiAccountData::Binary(
159 BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)),
160 UiAccountEncoding::Base64,
161 )
162 }
163 }
164 };
165 UiAccount {
166 lamports: account.lamports(),
167 data,
168 owner: *account.owner(),
169 executable: account.executable(),
170 rent_epoch: account.rent_epoch(),
171 space: Some(space as u64),
172 }
173}
174
175#[derive(Clone, Copy, Default)]
176pub struct AccountAdditionalDataV3 {
177 pub spl_token_additional_data: Option<SplTokenAdditionalDataV2>,
178}
179
180#[derive(Clone, Copy, Default)]
181pub struct SplTokenAdditionalData {
182 pub decimals: u8,
183 pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
184}
185
186impl SplTokenAdditionalData {
187 pub fn with_decimals(decimals: u8) -> Self {
188 Self {
189 decimals,
190 ..Default::default()
191 }
192 }
193}
194
195#[derive(Clone, Copy, Default)]
196pub struct SplTokenAdditionalDataV2 {
197 pub decimals: u8,
198 pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
199 pub scaled_ui_amount_config: Option<(ScaledUiAmountConfig, UnixTimestamp)>,
200}
201
202impl From<SplTokenAdditionalData> for SplTokenAdditionalDataV2 {
203 fn from(v: SplTokenAdditionalData) -> Self {
204 Self {
205 decimals: v.decimals,
206 interest_bearing_config: v.interest_bearing_config,
207 scaled_ui_amount_config: None,
208 }
209 }
210}
211
212impl SplTokenAdditionalDataV2 {
213 pub fn with_decimals(decimals: u8) -> Self {
214 Self {
215 decimals,
216 ..Default::default()
217 }
218 }
219}
220
221pub fn parse_account_data_v3(
222 pubkey: &Pubkey,
223 program_id: &Pubkey,
224 data: &[u8],
225 additional_data: Option<AccountAdditionalDataV3>,
226) -> Result<ParsedAccount, ParseAccountError> {
227 let program_name = PARSABLE_PROGRAM_IDS
228 .get(program_id)
229 .ok_or(ParseAccountError::ProgramNotParsable)?;
230 let additional_data = additional_data.unwrap_or_default();
231 let parsed_json = match program_name {
232 ParsableAccount::AddressLookupTable => {
233 serde_json::to_value(parse_address_lookup_table(data)?)?
234 }
235 ParsableAccount::BpfUpgradeableLoader => {
236 serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
237 }
238 ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
239 ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
240 ParsableAccount::SplToken | ParsableAccount::SplToken2022 => {
241 serde_json::to_value(parse_token_v3(
242 data,
243 additional_data.spl_token_additional_data.as_ref(),
244 )?)?
245 }
246 ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
247 ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
248 ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
249 };
250 Ok(ParsedAccount {
251 program: format!("{program_name:?}").to_kebab_case(),
252 parsed: parsed_json,
253 space: data.len() as u64,
254 })
255}
256
257#[cfg(test)]
258mod test {
259 use solana_nonce::state::Data;
260 use solana_nonce::state::State;
261 use solana_nonce::versions::Versions;
262 use solana_vote_interface::program::id as vote_program_id;
263 use solana_vote_interface::state::VoteStateV3;
264 use solana_vote_interface::state::VoteStateVersions;
265
266 use super::*;
267
268 #[test]
269 fn test_parse_account_data() {
270 let account_pubkey = solana_pubkey::new_rand();
271 let other_program = solana_pubkey::new_rand();
272 let data = vec![0; 4];
273 assert!(parse_account_data_v3(&account_pubkey, &other_program, &data, None).is_err());
274
275 let vote_state = VoteStateV3::default();
276 let mut vote_account_data: Vec<u8> = vec![0; VoteStateV3::size_of()];
277 let versioned = VoteStateVersions::new_v3(vote_state);
278 VoteStateV3::serialize(&versioned, &mut vote_account_data).unwrap();
279 let parsed = parse_account_data_v3(
280 &account_pubkey,
281 &vote_program_id(),
282 &vote_account_data,
283 None,
284 )
285 .unwrap();
286 assert_eq!(parsed.program, "vote".to_string());
287 assert_eq!(parsed.space, VoteStateV3::size_of() as u64);
288
289 let nonce_data = Versions::new(State::Initialized(Data::default()));
290 let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
291 let parsed = parse_account_data_v3(
292 &account_pubkey,
293 &system_program::id(),
294 &nonce_account_data,
295 None,
296 )
297 .unwrap();
298 assert_eq!(parsed.program, "nonce".to_string());
299 assert_eq!(parsed.space, State::size() as u64);
300 }
301}