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