1#![cfg(feature = "agave-unstable-api")]
2#![allow(clippy::arithmetic_side_effects)]
3
4pub mod parse_account_data;
5pub mod parse_address_lookup_table;
6pub mod parse_bpf_loader;
7#[allow(deprecated)]
8pub mod parse_config;
9pub mod parse_nonce;
10pub mod parse_stake;
11pub mod parse_sysvar;
12pub mod parse_token;
13pub mod parse_token_extension;
14pub mod parse_vote;
15pub mod validator_info;
16
17pub use solana_account_decoder_client_types::{
18 UiAccount, UiAccountData, UiAccountEncoding, UiDataSliceConfig,
19};
20use {
21 crate::parse_account_data::{AccountAdditionalDataV3, parse_account_data_v3},
22 base64::{Engine, prelude::BASE64_STANDARD},
23 serde::{Deserialize, Serialize},
24 solana_account::ReadableAccount,
25 solana_fee_calculator::FeeCalculator,
26 solana_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<AccountAdditionalDataV3>,
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_v3(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_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.to_account_shared_data().unwrap();
244 assert_eq!(decoded_account.data(), &vec![0; 1024]);
245 let decoded_account = encoded_account.to_account().unwrap();
246 assert_eq!(decoded_account.data(), &vec![0; 1024]);
247 }
248}