1use {
2 crate::{
3 parse_account_data::{ParsableAccount, ParseAccountError, SplTokenAdditionalDataV2},
4 parse_token_extension::parse_extension,
5 },
6 solana_program_option::COption,
7 solana_program_pack::Pack,
8 solana_pubkey::Pubkey,
9 spl_token_2022_interface::{
10 extension::{BaseStateWithExtensions, StateWithExtensions},
11 generic_token_account::GenericTokenAccount,
12 state::{Account, AccountState, Mint, Multisig},
13 },
14 std::str::FromStr,
15};
16pub use {
17 solana_account_decoder_client_types::token::{
18 TokenAccountType, UiAccountState, UiMint, UiMultisig, UiTokenAccount, UiTokenAmount,
19 real_number_string, real_number_string_trimmed,
20 },
21 spl_generic_token::{is_known_spl_token_id, spl_token_ids},
22};
23
24pub fn parse_token_v3(
25 data: &[u8],
26 additional_data: Option<&SplTokenAdditionalDataV2>,
27) -> Result<TokenAccountType, ParseAccountError> {
28 if let Ok(account) = StateWithExtensions::<Account>::unpack(data) {
29 let additional_data = additional_data.as_ref().ok_or_else(|| {
30 ParseAccountError::AdditionalDataMissing(
31 "no mint_decimals provided to parse spl-token account".to_string(),
32 )
33 })?;
34 let extension_types = account.get_extension_types().unwrap_or_default();
35 let ui_extensions = extension_types
36 .iter()
37 .map(|extension_type| parse_extension::<Account>(extension_type, &account))
38 .collect();
39 return Ok(TokenAccountType::Account(UiTokenAccount {
40 mint: account.base.mint.to_string(),
41 owner: account.base.owner.to_string(),
42 token_amount: token_amount_to_ui_amount_v3(account.base.amount, additional_data),
43 delegate: match account.base.delegate {
44 COption::Some(pubkey) => Some(pubkey.to_string()),
45 COption::None => None,
46 },
47 state: convert_account_state(account.base.state),
48 is_native: account.base.is_native(),
49 rent_exempt_reserve: match account.base.is_native {
50 COption::Some(reserve) => {
51 Some(token_amount_to_ui_amount_v3(reserve, additional_data))
52 }
53 COption::None => None,
54 },
55 delegated_amount: if account.base.delegate.is_none() {
56 None
57 } else {
58 Some(token_amount_to_ui_amount_v3(
59 account.base.delegated_amount,
60 additional_data,
61 ))
62 },
63 close_authority: match account.base.close_authority {
64 COption::Some(pubkey) => Some(pubkey.to_string()),
65 COption::None => None,
66 },
67 extensions: ui_extensions,
68 }));
69 }
70 if let Ok(mint) = StateWithExtensions::<Mint>::unpack(data) {
71 let extension_types = mint.get_extension_types().unwrap_or_default();
72 let ui_extensions = extension_types
73 .iter()
74 .map(|extension_type| parse_extension::<Mint>(extension_type, &mint))
75 .collect();
76 return Ok(TokenAccountType::Mint(UiMint {
77 mint_authority: match mint.base.mint_authority {
78 COption::Some(pubkey) => Some(pubkey.to_string()),
79 COption::None => None,
80 },
81 supply: mint.base.supply.to_string(),
82 decimals: mint.base.decimals,
83 is_initialized: mint.base.is_initialized,
84 freeze_authority: match mint.base.freeze_authority {
85 COption::Some(pubkey) => Some(pubkey.to_string()),
86 COption::None => None,
87 },
88 extensions: ui_extensions,
89 }));
90 }
91 if data.len() == Multisig::get_packed_len() {
92 let multisig = Multisig::unpack(data)
93 .map_err(|_| ParseAccountError::AccountNotParsable(ParsableAccount::SplToken))?;
94 Ok(TokenAccountType::Multisig(UiMultisig {
95 num_required_signers: multisig.m,
96 num_valid_signers: multisig.n,
97 is_initialized: multisig.is_initialized,
98 signers: multisig
99 .signers
100 .iter()
101 .filter_map(|pubkey| {
102 if pubkey != &Pubkey::default() {
103 Some(pubkey.to_string())
104 } else {
105 None
106 }
107 })
108 .collect(),
109 }))
110 } else {
111 Err(ParseAccountError::AccountNotParsable(
112 ParsableAccount::SplToken,
113 ))
114 }
115}
116
117pub fn convert_account_state(state: AccountState) -> UiAccountState {
118 match state {
119 AccountState::Uninitialized => UiAccountState::Uninitialized,
120 AccountState::Initialized => UiAccountState::Initialized,
121 AccountState::Frozen => UiAccountState::Frozen,
122 }
123}
124
125pub fn token_amount_to_ui_amount_v3(
126 amount: u64,
127 additional_data: &SplTokenAdditionalDataV2,
128) -> UiTokenAmount {
129 let decimals = additional_data.decimals;
130 let (ui_amount, ui_amount_string) = if let Some((interest_bearing_config, unix_timestamp)) =
131 additional_data.interest_bearing_config
132 {
133 let ui_amount_string =
134 interest_bearing_config.amount_to_ui_amount(amount, decimals, unix_timestamp);
135 (
136 ui_amount_string
137 .as_ref()
138 .and_then(|x| f64::from_str(x).ok()),
139 ui_amount_string.unwrap_or("".to_string()),
140 )
141 } else if let Some((scaled_ui_amount_config, unix_timestamp)) =
142 additional_data.scaled_ui_amount_config
143 {
144 let ui_amount_string =
145 scaled_ui_amount_config.amount_to_ui_amount(amount, decimals, unix_timestamp);
146 (
147 ui_amount_string
148 .as_ref()
149 .and_then(|x| f64::from_str(x).ok()),
150 ui_amount_string.unwrap_or("".to_string()),
151 )
152 } else {
153 let ui_amount = 10_usize
154 .checked_pow(decimals as u32)
155 .map(|dividend| amount as f64 / dividend as f64);
156 (ui_amount, real_number_string_trimmed(amount, decimals))
157 };
158 UiTokenAmount {
159 ui_amount,
160 decimals,
161 amount: amount.to_string(),
162 ui_amount_string,
163 }
164}
165
166pub fn get_token_account_mint(data: &[u8]) -> Option<Pubkey> {
167 Account::valid_account_data(data)
168 .then(|| Pubkey::try_from(data.get(..32)?).ok())
169 .flatten()
170}
171
172#[cfg(test)]
173mod test {
174 use {
175 super::*,
176 crate::parse_token_extension::{
177 UiMemoTransfer, UiMintCloseAuthority, UiPermissionedBurnConfig,
178 },
179 solana_account_decoder_client_types::token::UiExtension,
180 spl_token_2022_interface::extension::{
181 BaseStateWithExtensionsMut, ExtensionType, StateWithExtensionsMut,
182 immutable_owner::ImmutableOwner, interest_bearing_mint::InterestBearingConfig,
183 memo_transfer::MemoTransfer, mint_close_authority::MintCloseAuthority,
184 permissioned_burn::PermissionedBurnConfig, scaled_ui_amount::ScaledUiAmountConfig,
185 },
186 };
187
188 const INT_SECONDS_PER_YEAR: i64 = 6 * 6 * 24 * 36524;
189
190 #[test]
191 fn test_parse_token() {
192 let mint_pubkey = Pubkey::new_from_array([2; 32]);
193 let owner_pubkey = Pubkey::new_from_array([3; 32]);
194 let mut account_data = vec![0; Account::get_packed_len()];
195 let mut account = Account::unpack_unchecked(&account_data).unwrap();
196 account.mint = mint_pubkey;
197 account.owner = owner_pubkey;
198 account.amount = 42;
199 account.state = AccountState::Initialized;
200 account.is_native = COption::None;
201 account.close_authority = COption::Some(owner_pubkey);
202 Account::pack(account, &mut account_data).unwrap();
203
204 assert!(parse_token_v3(&account_data, None).is_err());
205 assert_eq!(
206 parse_token_v3(
207 &account_data,
208 Some(&SplTokenAdditionalDataV2::with_decimals(2))
209 )
210 .unwrap(),
211 TokenAccountType::Account(UiTokenAccount {
212 mint: mint_pubkey.to_string(),
213 owner: owner_pubkey.to_string(),
214 token_amount: UiTokenAmount {
215 ui_amount: Some(0.42),
216 decimals: 2,
217 amount: "42".to_string(),
218 ui_amount_string: "0.42".to_string()
219 },
220 delegate: None,
221 state: UiAccountState::Initialized,
222 is_native: false,
223 rent_exempt_reserve: None,
224 delegated_amount: None,
225 close_authority: Some(owner_pubkey.to_string()),
226 extensions: vec![],
227 }),
228 );
229
230 let mut mint_data = vec![0; Mint::get_packed_len()];
231 let mut mint = Mint::unpack_unchecked(&mint_data).unwrap();
232 mint.mint_authority = COption::Some(owner_pubkey);
233 mint.supply = 42;
234 mint.decimals = 3;
235 mint.is_initialized = true;
236 mint.freeze_authority = COption::Some(owner_pubkey);
237 Mint::pack(mint, &mut mint_data).unwrap();
238
239 assert_eq!(
240 parse_token_v3(&mint_data, None).unwrap(),
241 TokenAccountType::Mint(UiMint {
242 mint_authority: Some(owner_pubkey.to_string()),
243 supply: 42.to_string(),
244 decimals: 3,
245 is_initialized: true,
246 freeze_authority: Some(owner_pubkey.to_string()),
247 extensions: vec![],
248 }),
249 );
250
251 let signer1 = Pubkey::new_from_array([1; 32]);
252 let signer2 = Pubkey::new_from_array([2; 32]);
253 let signer3 = Pubkey::new_from_array([3; 32]);
254 let mut multisig_data = vec![0; Multisig::get_packed_len()];
255 let mut signers = [Pubkey::default(); 11];
256 signers[0] = signer1;
257 signers[1] = signer2;
258 signers[2] = signer3;
259 let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
260 multisig.m = 2;
261 multisig.n = 3;
262 multisig.is_initialized = true;
263 multisig.signers = signers;
264 Multisig::pack(multisig, &mut multisig_data).unwrap();
265
266 assert_eq!(
267 parse_token_v3(&multisig_data, None).unwrap(),
268 TokenAccountType::Multisig(UiMultisig {
269 num_required_signers: 2,
270 num_valid_signers: 3,
271 is_initialized: true,
272 signers: vec![
273 signer1.to_string(),
274 signer2.to_string(),
275 signer3.to_string()
276 ],
277 }),
278 );
279
280 let bad_data = vec![0; 4];
281 assert!(parse_token_v3(&bad_data, None).is_err());
282 }
283
284 #[test]
285 fn test_get_token_account_mint() {
286 let mint_pubkey = Pubkey::new_from_array([2; 32]);
287 let mut account_data = vec![0; Account::get_packed_len()];
288 let mut account = Account::unpack_unchecked(&account_data).unwrap();
289 account.mint = mint_pubkey;
290 account.state = AccountState::Initialized;
291 Account::pack(account, &mut account_data).unwrap();
292
293 let expected_mint_pubkey = Pubkey::from([2; 32]);
294 assert_eq!(
295 get_token_account_mint(&account_data),
296 Some(expected_mint_pubkey)
297 );
298 }
299
300 #[test]
301 fn test_ui_token_amount_real_string() {
302 assert_eq!(&real_number_string(1, 0), "1");
303 assert_eq!(&real_number_string_trimmed(1, 0), "1");
304 let token_amount =
305 token_amount_to_ui_amount_v3(1, &SplTokenAdditionalDataV2::with_decimals(0));
306 assert_eq!(
307 token_amount.ui_amount_string,
308 real_number_string_trimmed(1, 0)
309 );
310 assert_eq!(token_amount.ui_amount, Some(1.0));
311 assert_eq!(&real_number_string(10, 0), "10");
312 assert_eq!(&real_number_string_trimmed(10, 0), "10");
313 let token_amount =
314 token_amount_to_ui_amount_v3(10, &SplTokenAdditionalDataV2::with_decimals(0));
315 assert_eq!(
316 token_amount.ui_amount_string,
317 real_number_string_trimmed(10, 0)
318 );
319 assert_eq!(token_amount.ui_amount, Some(10.0));
320 assert_eq!(&real_number_string(1, 9), "0.000000001");
321 assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
322 let token_amount =
323 token_amount_to_ui_amount_v3(1, &SplTokenAdditionalDataV2::with_decimals(9));
324 assert_eq!(
325 token_amount.ui_amount_string,
326 real_number_string_trimmed(1, 9)
327 );
328 assert_eq!(token_amount.ui_amount, Some(0.000000001));
329 assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
330 assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
331 let token_amount = token_amount_to_ui_amount_v3(
332 1_000_000_000,
333 &SplTokenAdditionalDataV2::with_decimals(9),
334 );
335 assert_eq!(
336 token_amount.ui_amount_string,
337 real_number_string_trimmed(1_000_000_000, 9)
338 );
339 assert_eq!(token_amount.ui_amount, Some(1.0));
340 assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
341 assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
342 let token_amount = token_amount_to_ui_amount_v3(
343 1_234_567_890,
344 &SplTokenAdditionalDataV2::with_decimals(3),
345 );
346 assert_eq!(
347 token_amount.ui_amount_string,
348 real_number_string_trimmed(1_234_567_890, 3)
349 );
350 assert_eq!(token_amount.ui_amount, Some(1234567.89));
351 assert_eq!(
352 &real_number_string(1_234_567_890, 25),
353 "0.0000000000000001234567890"
354 );
355 assert_eq!(
356 &real_number_string_trimmed(1_234_567_890, 25),
357 "0.000000000000000123456789"
358 );
359 let token_amount = token_amount_to_ui_amount_v3(
360 1_234_567_890,
361 &SplTokenAdditionalDataV2::with_decimals(20),
362 );
363 assert_eq!(
364 token_amount.ui_amount_string,
365 real_number_string_trimmed(1_234_567_890, 20)
366 );
367 assert_eq!(token_amount.ui_amount, None);
368 }
369
370 #[test]
371 fn test_ui_token_amount_with_interest() {
372 let config = InterestBearingConfig {
374 initialization_timestamp: 0.into(),
375 pre_update_average_rate: 500.into(),
376 last_update_timestamp: INT_SECONDS_PER_YEAR.into(),
377 current_rate: 500.into(),
378 ..Default::default()
379 };
380 let additional_data = SplTokenAdditionalDataV2 {
381 decimals: 18,
382 interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR)),
383 ..Default::default()
384 };
385 const ONE: u64 = 1_000_000_000_000_000_000;
386 const TEN: u64 = 10_000_000_000_000_000_000;
387 let token_amount = token_amount_to_ui_amount_v3(ONE, &additional_data);
388 assert!(
389 token_amount
390 .ui_amount_string
391 .starts_with("1.051271096376024117")
392 );
393 assert!((token_amount.ui_amount.unwrap() - 1.0512710963760241f64).abs() < f64::EPSILON);
394 let token_amount = token_amount_to_ui_amount_v3(TEN, &additional_data);
395 assert!(
396 token_amount
397 .ui_amount_string
398 .starts_with("10.512710963760241611")
399 );
400 assert!((token_amount.ui_amount.unwrap() - 10.512710963760242f64).abs() < f64::EPSILON);
401
402 let config = InterestBearingConfig {
404 initialization_timestamp: 0.into(),
405 pre_update_average_rate: 32767.into(),
406 last_update_timestamp: 0.into(),
407 current_rate: 32767.into(),
408 ..Default::default()
409 };
410 let additional_data = SplTokenAdditionalDataV2 {
411 decimals: 0,
412 interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR * 1_000)),
413 ..Default::default()
414 };
415 let token_amount = token_amount_to_ui_amount_v3(u64::MAX, &additional_data);
416 assert_eq!(token_amount.ui_amount, Some(f64::INFINITY));
417 assert_eq!(token_amount.ui_amount_string, "inf");
418 }
419
420 #[test]
421 fn test_ui_token_amount_with_multiplier() {
422 let config = ScaledUiAmountConfig {
424 new_multiplier: 2f64.into(),
425 ..Default::default()
426 };
427 let additional_data = SplTokenAdditionalDataV2 {
428 decimals: 18,
429 scaled_ui_amount_config: Some((config, 0)),
430 ..Default::default()
431 };
432 const ONE: u64 = 1_000_000_000_000_000_000;
433 const TEN: u64 = 10_000_000_000_000_000_000;
434 let token_amount = token_amount_to_ui_amount_v3(ONE, &additional_data);
435 assert_eq!(token_amount.ui_amount_string, "2");
436 assert!(token_amount.ui_amount_string.starts_with("2"));
437 assert!((token_amount.ui_amount.unwrap() - 2.0).abs() < f64::EPSILON);
438 let token_amount = token_amount_to_ui_amount_v3(TEN, &additional_data);
439 assert!(token_amount.ui_amount_string.starts_with("20"));
440 assert!((token_amount.ui_amount.unwrap() - 20.0).abs() < f64::EPSILON);
441
442 let config = ScaledUiAmountConfig {
444 new_multiplier: f64::INFINITY.into(),
445 ..Default::default()
446 };
447 let additional_data = SplTokenAdditionalDataV2 {
448 decimals: 0,
449 scaled_ui_amount_config: Some((config, 0)),
450 ..Default::default()
451 };
452 let token_amount = token_amount_to_ui_amount_v3(u64::MAX, &additional_data);
453 assert_eq!(token_amount.ui_amount, Some(f64::INFINITY));
454 assert_eq!(token_amount.ui_amount_string, "inf");
455 }
456
457 #[test]
458 fn test_ui_token_amount_real_string_zero() {
459 assert_eq!(&real_number_string(0, 0), "0");
460 assert_eq!(&real_number_string_trimmed(0, 0), "0");
461 let token_amount =
462 token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(0));
463 assert_eq!(
464 token_amount.ui_amount_string,
465 real_number_string_trimmed(0, 0)
466 );
467 assert_eq!(token_amount.ui_amount, Some(0.0));
468 assert_eq!(&real_number_string(0, 9), "0.000000000");
469 assert_eq!(&real_number_string_trimmed(0, 9), "0");
470 let token_amount =
471 token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(9));
472 assert_eq!(
473 token_amount.ui_amount_string,
474 real_number_string_trimmed(0, 9)
475 );
476 assert_eq!(token_amount.ui_amount, Some(0.0));
477 assert_eq!(&real_number_string(0, 25), "0.0000000000000000000000000");
478 assert_eq!(&real_number_string_trimmed(0, 25), "0");
479 let token_amount =
480 token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(20));
481 assert_eq!(
482 token_amount.ui_amount_string,
483 real_number_string_trimmed(0, 20)
484 );
485 assert_eq!(token_amount.ui_amount, None);
486 }
487
488 #[test]
489 fn test_parse_token_account_with_extensions() {
490 let mint_pubkey = Pubkey::new_from_array([2; 32]);
491 let owner_pubkey = Pubkey::new_from_array([3; 32]);
492
493 let account_base = Account {
494 mint: mint_pubkey,
495 owner: owner_pubkey,
496 amount: 42,
497 state: AccountState::Initialized,
498 is_native: COption::None,
499 close_authority: COption::Some(owner_pubkey),
500 delegate: COption::None,
501 delegated_amount: 0,
502 };
503 let account_size = ExtensionType::try_calculate_account_len::<Account>(&[
504 ExtensionType::ImmutableOwner,
505 ExtensionType::MemoTransfer,
506 ])
507 .unwrap();
508 let mut account_data = vec![0; account_size];
509 let mut account_state =
510 StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data).unwrap();
511
512 account_state.base = account_base;
513 account_state.pack_base();
514 account_state.init_account_type().unwrap();
515
516 assert!(parse_token_v3(&account_data, None).is_err());
517 assert_eq!(
518 parse_token_v3(
519 &account_data,
520 Some(&SplTokenAdditionalDataV2::with_decimals(2))
521 )
522 .unwrap(),
523 TokenAccountType::Account(UiTokenAccount {
524 mint: mint_pubkey.to_string(),
525 owner: owner_pubkey.to_string(),
526 token_amount: UiTokenAmount {
527 ui_amount: Some(0.42),
528 decimals: 2,
529 amount: "42".to_string(),
530 ui_amount_string: "0.42".to_string()
531 },
532 delegate: None,
533 state: UiAccountState::Initialized,
534 is_native: false,
535 rent_exempt_reserve: None,
536 delegated_amount: None,
537 close_authority: Some(owner_pubkey.to_string()),
538 extensions: vec![],
539 }),
540 );
541
542 let mut account_data = vec![0; account_size];
543 let mut account_state =
544 StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data).unwrap();
545
546 account_state.base = account_base;
547 account_state.pack_base();
548 account_state.init_account_type().unwrap();
549
550 account_state
551 .init_extension::<ImmutableOwner>(true)
552 .unwrap();
553 let memo_transfer = account_state.init_extension::<MemoTransfer>(true).unwrap();
554 memo_transfer.require_incoming_transfer_memos = true.into();
555
556 assert!(parse_token_v3(&account_data, None).is_err());
557 assert_eq!(
558 parse_token_v3(
559 &account_data,
560 Some(&SplTokenAdditionalDataV2::with_decimals(2))
561 )
562 .unwrap(),
563 TokenAccountType::Account(UiTokenAccount {
564 mint: mint_pubkey.to_string(),
565 owner: owner_pubkey.to_string(),
566 token_amount: UiTokenAmount {
567 ui_amount: Some(0.42),
568 decimals: 2,
569 amount: "42".to_string(),
570 ui_amount_string: "0.42".to_string()
571 },
572 delegate: None,
573 state: UiAccountState::Initialized,
574 is_native: false,
575 rent_exempt_reserve: None,
576 delegated_amount: None,
577 close_authority: Some(owner_pubkey.to_string()),
578 extensions: vec![
579 UiExtension::ImmutableOwner,
580 UiExtension::MemoTransfer(UiMemoTransfer {
581 require_incoming_transfer_memos: true,
582 }),
583 ],
584 }),
585 );
586 }
587
588 #[test]
589 fn test_parse_token_mint_with_extensions() {
590 let owner_pubkey = Pubkey::new_from_array([3; 32]);
591 let mint_size =
592 ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MintCloseAuthority])
593 .unwrap();
594 let mint_base = Mint {
595 mint_authority: COption::Some(owner_pubkey),
596 supply: 42,
597 decimals: 3,
598 is_initialized: true,
599 freeze_authority: COption::Some(owner_pubkey),
600 };
601 let mut mint_data = vec![0; mint_size];
602 let mut mint_state =
603 StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
604
605 mint_state.base = mint_base;
606 mint_state.pack_base();
607 mint_state.init_account_type().unwrap();
608
609 assert_eq!(
610 parse_token_v3(&mint_data, None).unwrap(),
611 TokenAccountType::Mint(UiMint {
612 mint_authority: Some(owner_pubkey.to_string()),
613 supply: 42.to_string(),
614 decimals: 3,
615 is_initialized: true,
616 freeze_authority: Some(owner_pubkey.to_string()),
617 extensions: vec![],
618 }),
619 );
620
621 let mut mint_data = vec![0; mint_size];
622 let mut mint_state =
623 StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
624
625 let mint_close_authority = mint_state
626 .init_extension::<MintCloseAuthority>(true)
627 .unwrap();
628 mint_close_authority.close_authority = owner_pubkey.into();
629
630 mint_state.base = mint_base;
631 mint_state.pack_base();
632 mint_state.init_account_type().unwrap();
633
634 assert_eq!(
635 parse_token_v3(&mint_data, None).unwrap(),
636 TokenAccountType::Mint(UiMint {
637 mint_authority: Some(owner_pubkey.to_string()),
638 supply: 42.to_string(),
639 decimals: 3,
640 is_initialized: true,
641 freeze_authority: Some(owner_pubkey.to_string()),
642 extensions: vec![UiExtension::MintCloseAuthority(UiMintCloseAuthority {
643 close_authority: Some(owner_pubkey.to_string()),
644 })],
645 }),
646 );
647 }
648
649 #[test]
650 fn test_parse_token_mint_with_permissioned_burn() {
651 let owner_pubkey = Pubkey::new_from_array([3; 32]);
652 let authority_pubkey = Pubkey::new_from_array([4; 32]);
653 let mint_size =
654 ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::PermissionedBurn])
655 .unwrap();
656 let mint_base = Mint {
657 mint_authority: COption::Some(owner_pubkey),
658 supply: 42,
659 decimals: 3,
660 is_initialized: true,
661 freeze_authority: COption::Some(owner_pubkey),
662 };
663 let mut mint_data = vec![0; mint_size];
664 let mut mint_state =
665 StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
666
667 let permissioned_burn = mint_state
668 .init_extension::<PermissionedBurnConfig>(true)
669 .unwrap();
670 permissioned_burn.authority = authority_pubkey.into();
671
672 mint_state.base = mint_base;
673 mint_state.pack_base();
674 mint_state.init_account_type().unwrap();
675
676 assert_eq!(
677 parse_token_v3(&mint_data, None).unwrap(),
678 TokenAccountType::Mint(UiMint {
679 mint_authority: Some(owner_pubkey.to_string()),
680 supply: 42.to_string(),
681 decimals: 3,
682 is_initialized: true,
683 freeze_authority: Some(owner_pubkey.to_string()),
684 extensions: vec![UiExtension::PermissionedBurnConfig(
685 UiPermissionedBurnConfig {
686 authority: Some(authority_pubkey.to_string()),
687 }
688 )],
689 }),
690 );
691 }
692}