safecoin_account_decoder/
parse_account_data.rs1use {
2 crate::{
3 parse_address_lookup_table::parse_address_lookup_table,
4 parse_bpf_loader::parse_bpf_upgradeable_loader,
5 parse_config::parse_config,
6 parse_nonce::parse_nonce,
7 parse_stake::parse_stake,
8 parse_sysvar::parse_sysvar,
9 parse_token::{parse_token, safe_token_2022_id, safe_token_id},
10 parse_vote::parse_vote,
11 },
12 inflector::Inflector,
13 serde_json::Value,
14 solana_sdk::{instruction::InstructionError, pubkey::Pubkey, stake, system_program, sysvar},
15 std::collections::HashMap,
16 thiserror::Error,
17};
18
19lazy_static! {
20 static ref ADDRESS_LOOKUP_PROGRAM_ID: Pubkey = solana_address_lookup_table_program::id();
21 static ref BPF_UPGRADEABLE_LOADER_PROGRAM_ID: Pubkey = solana_sdk::bpf_loader_upgradeable::id();
22 static ref CONFIG_PROGRAM_ID: Pubkey = solana_config_program::id();
23 static ref STAKE_PROGRAM_ID: Pubkey = stake::program::id();
24 static ref SYSTEM_PROGRAM_ID: Pubkey = system_program::id();
25 static ref SYSVAR_PROGRAM_ID: Pubkey = sysvar::id();
26 static ref VOTE_PROGRAM_ID: Pubkey = solana_vote_program::id();
27 pub static ref PARSABLE_PROGRAM_IDS: HashMap<Pubkey, ParsableAccount> = {
28 let mut m = HashMap::new();
29 m.insert(
30 *ADDRESS_LOOKUP_PROGRAM_ID,
31 ParsableAccount::AddressLookupTable,
32 );
33 m.insert(
34 *BPF_UPGRADEABLE_LOADER_PROGRAM_ID,
35 ParsableAccount::BpfUpgradeableLoader,
36 );
37 m.insert(*CONFIG_PROGRAM_ID, ParsableAccount::Config);
38 m.insert(*SYSTEM_PROGRAM_ID, ParsableAccount::Nonce);
39 m.insert(safe_token_id(), ParsableAccount::SafeToken);
40 m.insert(safe_token_2022_id(), ParsableAccount::SafeToken2022);
41 m.insert(*STAKE_PROGRAM_ID, ParsableAccount::Stake);
42 m.insert(*SYSVAR_PROGRAM_ID, ParsableAccount::Sysvar);
43 m.insert(*VOTE_PROGRAM_ID, ParsableAccount::Vote);
44 m
45 };
46}
47
48#[derive(Error, Debug)]
49pub enum ParseAccountError {
50 #[error("{0:?} account not parsable")]
51 AccountNotParsable(ParsableAccount),
52
53 #[error("Program not parsable")]
54 ProgramNotParsable,
55
56 #[error("Additional data required to parse: {0}")]
57 AdditionalDataMissing(String),
58
59 #[error("Instruction error")]
60 InstructionError(#[from] InstructionError),
61
62 #[error("Serde json error")]
63 SerdeJsonError(#[from] serde_json::error::Error),
64}
65
66#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
67#[serde(rename_all = "camelCase")]
68pub struct ParsedAccount {
69 pub program: String,
70 pub parsed: Value,
71 pub space: u64,
72}
73
74#[derive(Debug, Serialize, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub enum ParsableAccount {
77 AddressLookupTable,
78 BpfUpgradeableLoader,
79 Config,
80 Nonce,
81 SafeToken,
82 SafeToken2022,
83 Stake,
84 Sysvar,
85 Vote,
86}
87
88#[derive(Default)]
89pub struct AccountAdditionalData {
90 pub safe_token_decimals: Option<u8>,
91}
92
93pub fn parse_account_data(
94 pubkey: &Pubkey,
95 program_id: &Pubkey,
96 data: &[u8],
97 additional_data: Option<AccountAdditionalData>,
98) -> Result<ParsedAccount, ParseAccountError> {
99 let program_name = PARSABLE_PROGRAM_IDS
100 .get(program_id)
101 .ok_or(ParseAccountError::ProgramNotParsable)?;
102 let additional_data = additional_data.unwrap_or_default();
103 let parsed_json = match program_name {
104 ParsableAccount::AddressLookupTable => {
105 serde_json::to_value(parse_address_lookup_table(data)?)?
106 }
107 ParsableAccount::BpfUpgradeableLoader => {
108 serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
109 }
110 ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
111 ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
112 ParsableAccount::SafeToken | ParsableAccount::SafeToken2022 => {
113 serde_json::to_value(parse_token(data, additional_data.safe_token_decimals)?)?
114 }
115 ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
116 ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
117 ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
118 };
119 Ok(ParsedAccount {
120 program: format!("{:?}", program_name).to_kebab_case(),
121 parsed: parsed_json,
122 space: data.len() as u64,
123 })
124}
125
126#[cfg(test)]
127mod test {
128 use {
129 super::*,
130 solana_sdk::nonce::{
131 state::{Data, Versions},
132 State,
133 },
134 solana_vote_program::vote_state::{VoteState, VoteStateVersions},
135 };
136
137 #[test]
138 fn test_parse_account_data() {
139 let account_pubkey = solana_sdk::pubkey::new_rand();
140 let other_program = solana_sdk::pubkey::new_rand();
141 let data = vec![0; 4];
142 assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err());
143
144 let vote_state = VoteState::default();
145 let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
146 let versioned = VoteStateVersions::new_current(vote_state);
147 VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
148 let parsed = parse_account_data(
149 &account_pubkey,
150 &solana_vote_program::id(),
151 &vote_account_data,
152 None,
153 )
154 .unwrap();
155 assert_eq!(parsed.program, "vote".to_string());
156 assert_eq!(parsed.space, VoteState::size_of() as u64);
157
158 let nonce_data = Versions::new(State::Initialized(Data::default()));
159 let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
160 let parsed = parse_account_data(
161 &account_pubkey,
162 &system_program::id(),
163 &nonce_account_data,
164 None,
165 )
166 .unwrap();
167 assert_eq!(parsed.program, "nonce".to_string());
168 assert_eq!(parsed.space, State::size() as u64);
169 }
170}