gemachain_account_decoder/
lib.rs1#![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#[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), 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, 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}