wasm_client_solana/solana_account_decoder/
parse_account_data.rs1use 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}