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