solana_account_decoder/
lib.rs

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