solana_extra_wasm/account_decoder/
mod.rs

1pub mod parse_token;
2pub mod parse_token_extension;
3
4use std::str::FromStr;
5
6use serde_json::Value;
7use solana_sdk::{
8    account::{ReadableAccount, WritableAccount},
9    clock::Epoch,
10    instruction::InstructionError,
11    pubkey::Pubkey,
12};
13// TODO:
14// use spl_token_2022::extension::{self, BaseState, ExtensionType, StateWithExtensions};
15use thiserror::Error;
16
17pub type StringAmount = String;
18pub type StringDecimals = String;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct UiDataSliceConfig {
23    pub offset: usize,
24    pub length: usize,
25}
26
27fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
28    if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
29        if offset >= data.len() {
30            &[]
31        } else if length > data.len() - offset {
32            &data[offset..]
33        } else {
34            &data[offset..offset + length]
35        }
36    } else {
37        data
38    }
39}
40
41/// A duplicate representation of an Account for pretty JSON serialization
42#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
43#[serde(rename_all = "camelCase")]
44pub struct UiAccount {
45    pub lamports: u64,
46    pub data: UiAccountData,
47    pub owner: String,
48    pub executable: bool,
49    pub rent_epoch: Epoch,
50}
51
52#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase", untagged)]
54pub enum UiAccountData {
55    LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility
56    Json(ParsedAccount),
57    Binary(String, UiAccountEncoding),
58}
59
60#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
61#[serde(rename_all = "camelCase")]
62pub enum UiAccountEncoding {
63    Binary, // Legacy. Retained for RPC backwards compatibility
64    Base58,
65    Base64,
66    JsonParsed,
67    // NOTE: Not supported in WASM
68    // #[serde(rename = "base64+zstd")]
69    // Base64Zstd,
70}
71
72pub const MAX_BASE58_BYTES: usize = 128;
73
74impl UiAccount {
75    fn encode_bs58<T: ReadableAccount>(
76        account: &T,
77        data_slice_config: Option<UiDataSliceConfig>,
78    ) -> String {
79        if account.data().len() <= MAX_BASE58_BYTES {
80            bs58::encode(slice_data(account.data(), data_slice_config)).into_string()
81        } else {
82            "error: data too large for bs58 encoding".to_string()
83        }
84    }
85
86    pub fn encode<T: ReadableAccount>(
87        pubkey: &Pubkey,
88        account: &T,
89        encoding: UiAccountEncoding,
90        additional_data: Option<AccountAdditionalData>,
91        data_slice_config: Option<UiDataSliceConfig>,
92    ) -> Self {
93        let data = match encoding {
94            UiAccountEncoding::Binary => {
95                let data = Self::encode_bs58(account, data_slice_config);
96                UiAccountData::LegacyBinary(data)
97            }
98            UiAccountEncoding::Base58 => {
99                let data = Self::encode_bs58(account, data_slice_config);
100                UiAccountData::Binary(data, encoding)
101            }
102            UiAccountEncoding::Base64 => UiAccountData::Binary(
103                base64::encode(slice_data(account.data(), data_slice_config)),
104                encoding,
105            ),
106            // NOTE: Not supported in WASM
107            // UiAccountEncoding::Base64Zstd => {
108            //     let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
109            //     match encoder
110            //         .write_all(slice_data(account.data(), data_slice_config))
111            //         .and_then(|()| encoder.finish())
112            //     {
113            //         Ok(zstd_data) => UiAccountData::Binary(base64::encode(zstd_data), encoding),
114            //         Err(_) => UiAccountData::Binary(
115            //             base64::encode(slice_data(account.data(), data_slice_config)),
116            //             UiAccountEncoding::Base64,
117            //         ),
118            //     }
119            // }
120            UiAccountEncoding::JsonParsed => {
121                if let Ok(parsed_data) =
122                    parse_account_data(pubkey, account.owner(), account.data(), additional_data)
123                {
124                    UiAccountData::Json(parsed_data)
125                } else {
126                    UiAccountData::Binary(base64::encode(account.data()), UiAccountEncoding::Base64)
127                }
128            }
129        };
130        UiAccount {
131            lamports: account.lamports(),
132            data,
133            owner: account.owner().to_string(),
134            executable: account.executable(),
135            rent_epoch: account.rent_epoch(),
136        }
137    }
138
139    pub fn decode<T: WritableAccount>(&self) -> Option<T> {
140        let data = match &self.data {
141            UiAccountData::Json(_) => None,
142            UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
143            UiAccountData::Binary(blob, encoding) => match encoding {
144                UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
145                UiAccountEncoding::Base64 => base64::decode(blob).ok(),
146                // NOTE: Not supported in WASM
147                // UiAccountEncoding::Base64Zstd => base64::decode(blob).ok().and_then(|zstd_data| {
148                //     let mut data = vec![];
149                //     zstd::stream::read::Decoder::new(zstd_data.as_slice())
150                //         .and_then(|mut reader| reader.read_to_end(&mut data))
151                //         .map(|_| data)
152                //         .ok()
153                // }),
154                UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
155            },
156        }?;
157        Some(T::create(
158            self.lamports,
159            data,
160            Pubkey::from_str(&self.owner).ok()?,
161            self.executable,
162            self.rent_epoch,
163        ))
164    }
165}
166
167#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
168#[serde(rename_all = "camelCase")]
169pub struct ParsedAccount {
170    pub program: String,
171    pub parsed: Value,
172    pub space: u64,
173}
174
175#[derive(Default)]
176pub struct AccountAdditionalData {
177    pub spl_token_decimals: Option<u8>,
178}
179
180#[derive(Debug, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub enum ParsableAccount {
183    BpfUpgradeableLoader,
184    Config,
185    Nonce,
186    SplToken,
187    SplToken2022,
188    Stake,
189    Sysvar,
190    Vote,
191}
192
193#[derive(Error, Debug)]
194pub enum ParseAccountError {
195    #[error("{0:?} account not parsable")]
196    AccountNotParsable(ParsableAccount),
197
198    #[error("Program not parsable")]
199    ProgramNotParsable,
200
201    #[error("Additional data required to parse: {0}")]
202    AdditionalDataMissing(String),
203
204    #[error("Instruction error")]
205    InstructionError(#[from] InstructionError),
206
207    #[error("Serde json error")]
208    SerdeJsonError(#[from] serde_json::error::Error),
209}
210
211pub fn parse_account_data(
212    _pubkey: &Pubkey,
213    _program_id: &Pubkey,
214    _data: &[u8],
215    _additional_data: Option<AccountAdditionalData>,
216) -> Result<ParsedAccount, ParseAccountError> {
217    // TODO:
218    Err(ParseAccountError::ProgramNotParsable)
219    // let program_name = PARSABLE_PROGRAM_IDS
220    //     .get(program_id)
221    //     .ok_or(ParseAccountError::ProgramNotParsable)?;
222    // let additional_data = additional_data.unwrap_or_default();
223    // let parsed_json = match program_name {
224    //     ParsableAccount::BpfUpgradeableLoader => {
225    //         serde_json::to_value(parse_bpf_upgradeable_loader(data)?)?
226    //     }
227    //     ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
228    //     ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
229    //     ParsableAccount::SplToken | ParsableAccount::SplToken2022 => {
230    //         serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
231    //     }
232    //     ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
233    //     ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
234    //     ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
235    // };
236    // Ok(ParsedAccount {
237    //     program: format!("{:?}", program_name).to_kebab_case(),
238    //     parsed: parsed_json,
239    //     space: data.len() as u64,
240    // })
241}