solana_account_decoder/
lib.rs

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        // Whole account
185        assert_eq!(
186            encode_bs58(&account, None),
187            "error: data too large for bs58 encoding"
188        );
189
190        // Slice of account that's still too large
191        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        // Slice of account that fits inside `MAX_BASE58_BYTES`
203        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        // Slice of account that's too large, but whose intersection with the account still fits
215        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}