gemachain_account_decoder/
lib.rs

1#![allow(clippy::integer_arithmetic)]
2#[macro_use]
3extern crate lazy_static;
4#[macro_use]
5extern crate serde_derive;
6
7pub mod parse_account_data;
8pub mod parse_bpf_loader;
9pub mod parse_config;
10pub mod parse_nonce;
11pub mod parse_stake;
12pub mod parse_sysvar;
13pub mod parse_token;
14pub mod parse_vote;
15pub mod validator_info;
16
17use {
18    crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount},
19    gemachain_sdk::{
20        account::ReadableAccount, account::WritableAccount, clock::Epoch,
21        fee_calculator::FeeCalculator, pubkey::Pubkey,
22    },
23    std::{
24        io::{Read, Write},
25        str::FromStr,
26    },
27};
28
29pub type StringAmount = String;
30pub type StringDecimals = String;
31pub const MAX_BASE58_BYTES: usize = 128;
32
33/// A duplicate representation of an Account for pretty JSON serialization
34#[derive(Serialize, Deserialize, Clone, Debug)]
35#[serde(rename_all = "camelCase")]
36pub struct UiAccount {
37    pub carats: u64,
38    pub data: UiAccountData,
39    pub owner: String,
40    pub executable: bool,
41    pub rent_epoch: Epoch,
42}
43
44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45#[serde(rename_all = "camelCase", untagged)]
46pub enum UiAccountData {
47    LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility
48    Json(ParsedAccount),
49    Binary(String, UiAccountEncoding),
50}
51
52#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
53#[serde(rename_all = "camelCase")]
54pub enum UiAccountEncoding {
55    Binary, // Legacy. Retained for RPC backwards compatibility
56    Base58,
57    Base64,
58    JsonParsed,
59    #[serde(rename = "base64+zstd")]
60    Base64Zstd,
61}
62
63impl UiAccount {
64    fn encode_bs58<T: ReadableAccount>(
65        account: &T,
66        data_slice_config: Option<UiDataSliceConfig>,
67    ) -> String {
68        if account.data().len() <= MAX_BASE58_BYTES {
69            bs58::encode(slice_data(account.data(), data_slice_config)).into_string()
70        } else {
71            "error: data too large for bs58 encoding".to_string()
72        }
73    }
74
75    pub fn encode<T: ReadableAccount>(
76        pubkey: &Pubkey,
77        account: &T,
78        encoding: UiAccountEncoding,
79        additional_data: Option<AccountAdditionalData>,
80        data_slice_config: Option<UiDataSliceConfig>,
81    ) -> Self {
82        let data = match encoding {
83            UiAccountEncoding::Binary => {
84                let data = Self::encode_bs58(account, data_slice_config);
85                UiAccountData::LegacyBinary(data)
86            }
87            UiAccountEncoding::Base58 => {
88                let data = Self::encode_bs58(account, data_slice_config);
89                UiAccountData::Binary(data, encoding)
90            }
91            UiAccountEncoding::Base64 => UiAccountData::Binary(
92                base64::encode(slice_data(account.data(), data_slice_config)),
93                encoding,
94            ),
95            UiAccountEncoding::Base64Zstd => {
96                let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap();
97                match encoder
98                    .write_all(slice_data(account.data(), data_slice_config))
99                    .and_then(|()| encoder.finish())
100                {
101                    Ok(zstd_data) => UiAccountData::Binary(base64::encode(zstd_data), encoding),
102                    Err(_) => UiAccountData::Binary(
103                        base64::encode(slice_data(account.data(), data_slice_config)),
104                        UiAccountEncoding::Base64,
105                    ),
106                }
107            }
108            UiAccountEncoding::JsonParsed => {
109                if let Ok(parsed_data) =
110                    parse_account_data(pubkey, account.owner(), account.data(), additional_data)
111                {
112                    UiAccountData::Json(parsed_data)
113                } else {
114                    UiAccountData::Binary(
115                        base64::encode(&account.data()),
116                        UiAccountEncoding::Base64,
117                    )
118                }
119            }
120        };
121        UiAccount {
122            carats: account.carats(),
123            data,
124            owner: account.owner().to_string(),
125            executable: account.executable(),
126            rent_epoch: account.rent_epoch(),
127        }
128    }
129
130    pub fn decode<T: WritableAccount>(&self) -> Option<T> {
131        let data = match &self.data {
132            UiAccountData::Json(_) => None,
133            UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
134            UiAccountData::Binary(blob, encoding) => match encoding {
135                UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
136                UiAccountEncoding::Base64 => base64::decode(blob).ok(),
137                UiAccountEncoding::Base64Zstd => base64::decode(blob)
138                    .ok()
139                    .map(|zstd_data| {
140                        let mut data = vec![];
141                        zstd::stream::read::Decoder::new(zstd_data.as_slice())
142                            .and_then(|mut reader| reader.read_to_end(&mut data))
143                            .map(|_| data)
144                            .ok()
145                    })
146                    .flatten(),
147                UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
148            },
149        }?;
150        Some(T::create(
151            self.carats,
152            data,
153            Pubkey::from_str(&self.owner).ok()?,
154            self.executable,
155            self.rent_epoch,
156        ))
157    }
158}
159
160#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
161#[serde(rename_all = "camelCase")]
162pub struct UiFeeCalculator {
163    pub carats_per_signature: StringAmount,
164}
165
166impl From<FeeCalculator> for UiFeeCalculator {
167    fn from(fee_calculator: FeeCalculator) -> Self {
168        Self {
169            carats_per_signature: fee_calculator.carats_per_signature.to_string(),
170        }
171    }
172}
173
174impl Default for UiFeeCalculator {
175    fn default() -> Self {
176        Self {
177            carats_per_signature: "0".to_string(),
178        }
179    }
180}
181
182#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
183#[serde(rename_all = "camelCase")]
184pub struct UiDataSliceConfig {
185    pub offset: usize,
186    pub length: usize,
187}
188
189fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
190    if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
191        if offset >= data.len() {
192            &[]
193        } else if length > data.len() - offset {
194            &data[offset..]
195        } else {
196            &data[offset..offset + length]
197        }
198    } else {
199        data
200    }
201}
202
203#[cfg(test)]
204mod test {
205    use super::*;
206    use gemachain_sdk::account::{Account, AccountSharedData};
207
208    #[test]
209    fn test_slice_data() {
210        let data = vec![1, 2, 3, 4, 5];
211        let slice_config = Some(UiDataSliceConfig {
212            offset: 0,
213            length: 5,
214        });
215        assert_eq!(slice_data(&data, slice_config), &data[..]);
216
217        let slice_config = Some(UiDataSliceConfig {
218            offset: 0,
219            length: 10,
220        });
221        assert_eq!(slice_data(&data, slice_config), &data[..]);
222
223        let slice_config = Some(UiDataSliceConfig {
224            offset: 1,
225            length: 2,
226        });
227        assert_eq!(slice_data(&data, slice_config), &data[1..3]);
228
229        let slice_config = Some(UiDataSliceConfig {
230            offset: 10,
231            length: 2,
232        });
233        assert_eq!(slice_data(&data, slice_config), &[] as &[u8]);
234    }
235
236    #[test]
237    fn test_base64_zstd() {
238        let encoded_account = UiAccount::encode(
239            &Pubkey::default(),
240            &AccountSharedData::from(Account {
241                data: vec![0; 1024],
242                ..Account::default()
243            }),
244            UiAccountEncoding::Base64Zstd,
245            None,
246            None,
247        );
248        assert!(matches!(
249            encoded_account.data,
250            UiAccountData::Binary(_, UiAccountEncoding::Base64Zstd)
251        ));
252
253        let decoded_account = encoded_account.decode::<Account>().unwrap();
254        assert_eq!(decoded_account.data(), &vec![0; 1024]);
255        let decoded_account = encoded_account.decode::<AccountSharedData>().unwrap();
256        assert_eq!(decoded_account.data(), &vec![0; 1024]);
257    }
258}