gemachain_account_decoder/
parse_account_data.rs

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