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