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