safecoin_account_decoder/
parse_account_data.rs

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