solana_account_decoder/
parse_token.rs

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        real_number_string, real_number_string_trimmed, TokenAccountType, UiAccountState, UiMint,
19        UiMultisig, UiTokenAccount, UiTokenAmount,
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::{UiMemoTransfer, UiMintCloseAuthority},
177        solana_account_decoder_client_types::token::UiExtension,
178        spl_pod::optional_keys::OptionalNonZeroPubkey,
179        spl_token_2022_interface::extension::{
180            immutable_owner::ImmutableOwner, interest_bearing_mint::InterestBearingConfig,
181            memo_transfer::MemoTransfer, mint_close_authority::MintCloseAuthority,
182            scaled_ui_amount::ScaledUiAmountConfig, BaseStateWithExtensionsMut, ExtensionType,
183            StateWithExtensionsMut,
184        },
185    };
186
187    const INT_SECONDS_PER_YEAR: i64 = 6 * 6 * 24 * 36524;
188
189    #[test]
190    fn test_parse_token() {
191        let mint_pubkey = Pubkey::new_from_array([2; 32]);
192        let owner_pubkey = Pubkey::new_from_array([3; 32]);
193        let mut account_data = vec![0; Account::get_packed_len()];
194        let mut account = Account::unpack_unchecked(&account_data).unwrap();
195        account.mint = mint_pubkey;
196        account.owner = owner_pubkey;
197        account.amount = 42;
198        account.state = AccountState::Initialized;
199        account.is_native = COption::None;
200        account.close_authority = COption::Some(owner_pubkey);
201        Account::pack(account, &mut account_data).unwrap();
202
203        assert!(parse_token_v3(&account_data, None).is_err());
204        assert_eq!(
205            parse_token_v3(
206                &account_data,
207                Some(&SplTokenAdditionalDataV2::with_decimals(2))
208            )
209            .unwrap(),
210            TokenAccountType::Account(UiTokenAccount {
211                mint: mint_pubkey.to_string(),
212                owner: owner_pubkey.to_string(),
213                token_amount: UiTokenAmount {
214                    ui_amount: Some(0.42),
215                    decimals: 2,
216                    amount: "42".to_string(),
217                    ui_amount_string: "0.42".to_string()
218                },
219                delegate: None,
220                state: UiAccountState::Initialized,
221                is_native: false,
222                rent_exempt_reserve: None,
223                delegated_amount: None,
224                close_authority: Some(owner_pubkey.to_string()),
225                extensions: vec![],
226            }),
227        );
228
229        let mut mint_data = vec![0; Mint::get_packed_len()];
230        let mut mint = Mint::unpack_unchecked(&mint_data).unwrap();
231        mint.mint_authority = COption::Some(owner_pubkey);
232        mint.supply = 42;
233        mint.decimals = 3;
234        mint.is_initialized = true;
235        mint.freeze_authority = COption::Some(owner_pubkey);
236        Mint::pack(mint, &mut mint_data).unwrap();
237
238        assert_eq!(
239            parse_token_v3(&mint_data, None).unwrap(),
240            TokenAccountType::Mint(UiMint {
241                mint_authority: Some(owner_pubkey.to_string()),
242                supply: 42.to_string(),
243                decimals: 3,
244                is_initialized: true,
245                freeze_authority: Some(owner_pubkey.to_string()),
246                extensions: vec![],
247            }),
248        );
249
250        let signer1 = Pubkey::new_from_array([1; 32]);
251        let signer2 = Pubkey::new_from_array([2; 32]);
252        let signer3 = Pubkey::new_from_array([3; 32]);
253        let mut multisig_data = vec![0; Multisig::get_packed_len()];
254        let mut signers = [Pubkey::default(); 11];
255        signers[0] = signer1;
256        signers[1] = signer2;
257        signers[2] = signer3;
258        let mut multisig = Multisig::unpack_unchecked(&multisig_data).unwrap();
259        multisig.m = 2;
260        multisig.n = 3;
261        multisig.is_initialized = true;
262        multisig.signers = signers;
263        Multisig::pack(multisig, &mut multisig_data).unwrap();
264
265        assert_eq!(
266            parse_token_v3(&multisig_data, None).unwrap(),
267            TokenAccountType::Multisig(UiMultisig {
268                num_required_signers: 2,
269                num_valid_signers: 3,
270                is_initialized: true,
271                signers: vec![
272                    signer1.to_string(),
273                    signer2.to_string(),
274                    signer3.to_string()
275                ],
276            }),
277        );
278
279        let bad_data = vec![0; 4];
280        assert!(parse_token_v3(&bad_data, None).is_err());
281    }
282
283    #[test]
284    fn test_get_token_account_mint() {
285        let mint_pubkey = Pubkey::new_from_array([2; 32]);
286        let mut account_data = vec![0; Account::get_packed_len()];
287        let mut account = Account::unpack_unchecked(&account_data).unwrap();
288        account.mint = mint_pubkey;
289        account.state = AccountState::Initialized;
290        Account::pack(account, &mut account_data).unwrap();
291
292        let expected_mint_pubkey = Pubkey::from([2; 32]);
293        assert_eq!(
294            get_token_account_mint(&account_data),
295            Some(expected_mint_pubkey)
296        );
297    }
298
299    #[test]
300    fn test_ui_token_amount_real_string() {
301        assert_eq!(&real_number_string(1, 0), "1");
302        assert_eq!(&real_number_string_trimmed(1, 0), "1");
303        let token_amount =
304            token_amount_to_ui_amount_v3(1, &SplTokenAdditionalDataV2::with_decimals(0));
305        assert_eq!(
306            token_amount.ui_amount_string,
307            real_number_string_trimmed(1, 0)
308        );
309        assert_eq!(token_amount.ui_amount, Some(1.0));
310        assert_eq!(&real_number_string(10, 0), "10");
311        assert_eq!(&real_number_string_trimmed(10, 0), "10");
312        let token_amount =
313            token_amount_to_ui_amount_v3(10, &SplTokenAdditionalDataV2::with_decimals(0));
314        assert_eq!(
315            token_amount.ui_amount_string,
316            real_number_string_trimmed(10, 0)
317        );
318        assert_eq!(token_amount.ui_amount, Some(10.0));
319        assert_eq!(&real_number_string(1, 9), "0.000000001");
320        assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
321        let token_amount =
322            token_amount_to_ui_amount_v3(1, &SplTokenAdditionalDataV2::with_decimals(9));
323        assert_eq!(
324            token_amount.ui_amount_string,
325            real_number_string_trimmed(1, 9)
326        );
327        assert_eq!(token_amount.ui_amount, Some(0.000000001));
328        assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
329        assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
330        let token_amount = token_amount_to_ui_amount_v3(
331            1_000_000_000,
332            &SplTokenAdditionalDataV2::with_decimals(9),
333        );
334        assert_eq!(
335            token_amount.ui_amount_string,
336            real_number_string_trimmed(1_000_000_000, 9)
337        );
338        assert_eq!(token_amount.ui_amount, Some(1.0));
339        assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
340        assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
341        let token_amount = token_amount_to_ui_amount_v3(
342            1_234_567_890,
343            &SplTokenAdditionalDataV2::with_decimals(3),
344        );
345        assert_eq!(
346            token_amount.ui_amount_string,
347            real_number_string_trimmed(1_234_567_890, 3)
348        );
349        assert_eq!(token_amount.ui_amount, Some(1234567.89));
350        assert_eq!(
351            &real_number_string(1_234_567_890, 25),
352            "0.0000000000000001234567890"
353        );
354        assert_eq!(
355            &real_number_string_trimmed(1_234_567_890, 25),
356            "0.000000000000000123456789"
357        );
358        let token_amount = token_amount_to_ui_amount_v3(
359            1_234_567_890,
360            &SplTokenAdditionalDataV2::with_decimals(20),
361        );
362        assert_eq!(
363            token_amount.ui_amount_string,
364            real_number_string_trimmed(1_234_567_890, 20)
365        );
366        assert_eq!(token_amount.ui_amount, None);
367    }
368
369    #[test]
370    fn test_ui_token_amount_with_interest() {
371        // constant 5%
372        let config = InterestBearingConfig {
373            initialization_timestamp: 0.into(),
374            pre_update_average_rate: 500.into(),
375            last_update_timestamp: INT_SECONDS_PER_YEAR.into(),
376            current_rate: 500.into(),
377            ..Default::default()
378        };
379        let additional_data = SplTokenAdditionalDataV2 {
380            decimals: 18,
381            interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR)),
382            ..Default::default()
383        };
384        const ONE: u64 = 1_000_000_000_000_000_000;
385        const TEN: u64 = 10_000_000_000_000_000_000;
386        let token_amount = token_amount_to_ui_amount_v3(ONE, &additional_data);
387        assert!(token_amount
388            .ui_amount_string
389            .starts_with("1.051271096376024117"));
390        assert!((token_amount.ui_amount.unwrap() - 1.0512710963760241f64).abs() < f64::EPSILON);
391        let token_amount = token_amount_to_ui_amount_v3(TEN, &additional_data);
392        assert!(token_amount
393            .ui_amount_string
394            .starts_with("10.512710963760241611"));
395        assert!((token_amount.ui_amount.unwrap() - 10.512710963760242f64).abs() < f64::EPSILON);
396
397        // huge case
398        let config = InterestBearingConfig {
399            initialization_timestamp: 0.into(),
400            pre_update_average_rate: 32767.into(),
401            last_update_timestamp: 0.into(),
402            current_rate: 32767.into(),
403            ..Default::default()
404        };
405        let additional_data = SplTokenAdditionalDataV2 {
406            decimals: 0,
407            interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR * 1_000)),
408            ..Default::default()
409        };
410        let token_amount = token_amount_to_ui_amount_v3(u64::MAX, &additional_data);
411        assert_eq!(token_amount.ui_amount, Some(f64::INFINITY));
412        assert_eq!(token_amount.ui_amount_string, "inf");
413    }
414
415    #[test]
416    fn test_ui_token_amount_with_multiplier() {
417        // 2x multiplier
418        let config = ScaledUiAmountConfig {
419            new_multiplier: 2f64.into(),
420            ..Default::default()
421        };
422        let additional_data = SplTokenAdditionalDataV2 {
423            decimals: 18,
424            scaled_ui_amount_config: Some((config, 0)),
425            ..Default::default()
426        };
427        const ONE: u64 = 1_000_000_000_000_000_000;
428        const TEN: u64 = 10_000_000_000_000_000_000;
429        let token_amount = token_amount_to_ui_amount_v3(ONE, &additional_data);
430        assert_eq!(token_amount.ui_amount_string, "2");
431        assert!(token_amount.ui_amount_string.starts_with("2"));
432        assert!((token_amount.ui_amount.unwrap() - 2.0).abs() < f64::EPSILON);
433        let token_amount = token_amount_to_ui_amount_v3(TEN, &additional_data);
434        assert!(token_amount.ui_amount_string.starts_with("20"));
435        assert!((token_amount.ui_amount.unwrap() - 20.0).abs() < f64::EPSILON);
436
437        // huge case
438        let config = ScaledUiAmountConfig {
439            new_multiplier: f64::INFINITY.into(),
440            ..Default::default()
441        };
442        let additional_data = SplTokenAdditionalDataV2 {
443            decimals: 0,
444            scaled_ui_amount_config: Some((config, 0)),
445            ..Default::default()
446        };
447        let token_amount = token_amount_to_ui_amount_v3(u64::MAX, &additional_data);
448        assert_eq!(token_amount.ui_amount, Some(f64::INFINITY));
449        assert_eq!(token_amount.ui_amount_string, "inf");
450    }
451
452    #[test]
453    fn test_ui_token_amount_real_string_zero() {
454        assert_eq!(&real_number_string(0, 0), "0");
455        assert_eq!(&real_number_string_trimmed(0, 0), "0");
456        let token_amount =
457            token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(0));
458        assert_eq!(
459            token_amount.ui_amount_string,
460            real_number_string_trimmed(0, 0)
461        );
462        assert_eq!(token_amount.ui_amount, Some(0.0));
463        assert_eq!(&real_number_string(0, 9), "0.000000000");
464        assert_eq!(&real_number_string_trimmed(0, 9), "0");
465        let token_amount =
466            token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(9));
467        assert_eq!(
468            token_amount.ui_amount_string,
469            real_number_string_trimmed(0, 9)
470        );
471        assert_eq!(token_amount.ui_amount, Some(0.0));
472        assert_eq!(&real_number_string(0, 25), "0.0000000000000000000000000");
473        assert_eq!(&real_number_string_trimmed(0, 25), "0");
474        let token_amount =
475            token_amount_to_ui_amount_v3(0, &SplTokenAdditionalDataV2::with_decimals(20));
476        assert_eq!(
477            token_amount.ui_amount_string,
478            real_number_string_trimmed(0, 20)
479        );
480        assert_eq!(token_amount.ui_amount, None);
481    }
482
483    #[test]
484    fn test_parse_token_account_with_extensions() {
485        let mint_pubkey = Pubkey::new_from_array([2; 32]);
486        let owner_pubkey = Pubkey::new_from_array([3; 32]);
487
488        let account_base = Account {
489            mint: mint_pubkey,
490            owner: owner_pubkey,
491            amount: 42,
492            state: AccountState::Initialized,
493            is_native: COption::None,
494            close_authority: COption::Some(owner_pubkey),
495            delegate: COption::None,
496            delegated_amount: 0,
497        };
498        let account_size = ExtensionType::try_calculate_account_len::<Account>(&[
499            ExtensionType::ImmutableOwner,
500            ExtensionType::MemoTransfer,
501        ])
502        .unwrap();
503        let mut account_data = vec![0; account_size];
504        let mut account_state =
505            StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data).unwrap();
506
507        account_state.base = account_base;
508        account_state.pack_base();
509        account_state.init_account_type().unwrap();
510
511        assert!(parse_token_v3(&account_data, None).is_err());
512        assert_eq!(
513            parse_token_v3(
514                &account_data,
515                Some(&SplTokenAdditionalDataV2::with_decimals(2))
516            )
517            .unwrap(),
518            TokenAccountType::Account(UiTokenAccount {
519                mint: mint_pubkey.to_string(),
520                owner: owner_pubkey.to_string(),
521                token_amount: UiTokenAmount {
522                    ui_amount: Some(0.42),
523                    decimals: 2,
524                    amount: "42".to_string(),
525                    ui_amount_string: "0.42".to_string()
526                },
527                delegate: None,
528                state: UiAccountState::Initialized,
529                is_native: false,
530                rent_exempt_reserve: None,
531                delegated_amount: None,
532                close_authority: Some(owner_pubkey.to_string()),
533                extensions: vec![],
534            }),
535        );
536
537        let mut account_data = vec![0; account_size];
538        let mut account_state =
539            StateWithExtensionsMut::<Account>::unpack_uninitialized(&mut account_data).unwrap();
540
541        account_state.base = account_base;
542        account_state.pack_base();
543        account_state.init_account_type().unwrap();
544
545        account_state
546            .init_extension::<ImmutableOwner>(true)
547            .unwrap();
548        let memo_transfer = account_state.init_extension::<MemoTransfer>(true).unwrap();
549        memo_transfer.require_incoming_transfer_memos = true.into();
550
551        assert!(parse_token_v3(&account_data, None).is_err());
552        assert_eq!(
553            parse_token_v3(
554                &account_data,
555                Some(&SplTokenAdditionalDataV2::with_decimals(2))
556            )
557            .unwrap(),
558            TokenAccountType::Account(UiTokenAccount {
559                mint: mint_pubkey.to_string(),
560                owner: owner_pubkey.to_string(),
561                token_amount: UiTokenAmount {
562                    ui_amount: Some(0.42),
563                    decimals: 2,
564                    amount: "42".to_string(),
565                    ui_amount_string: "0.42".to_string()
566                },
567                delegate: None,
568                state: UiAccountState::Initialized,
569                is_native: false,
570                rent_exempt_reserve: None,
571                delegated_amount: None,
572                close_authority: Some(owner_pubkey.to_string()),
573                extensions: vec![
574                    UiExtension::ImmutableOwner,
575                    UiExtension::MemoTransfer(UiMemoTransfer {
576                        require_incoming_transfer_memos: true,
577                    }),
578                ],
579            }),
580        );
581    }
582
583    #[test]
584    fn test_parse_token_mint_with_extensions() {
585        let owner_pubkey = Pubkey::new_from_array([3; 32]);
586        let mint_size =
587            ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MintCloseAuthority])
588                .unwrap();
589        let mint_base = Mint {
590            mint_authority: COption::Some(owner_pubkey),
591            supply: 42,
592            decimals: 3,
593            is_initialized: true,
594            freeze_authority: COption::Some(owner_pubkey),
595        };
596        let mut mint_data = vec![0; mint_size];
597        let mut mint_state =
598            StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
599
600        mint_state.base = mint_base;
601        mint_state.pack_base();
602        mint_state.init_account_type().unwrap();
603
604        assert_eq!(
605            parse_token_v3(&mint_data, None).unwrap(),
606            TokenAccountType::Mint(UiMint {
607                mint_authority: Some(owner_pubkey.to_string()),
608                supply: 42.to_string(),
609                decimals: 3,
610                is_initialized: true,
611                freeze_authority: Some(owner_pubkey.to_string()),
612                extensions: vec![],
613            }),
614        );
615
616        let mut mint_data = vec![0; mint_size];
617        let mut mint_state =
618            StateWithExtensionsMut::<Mint>::unpack_uninitialized(&mut mint_data).unwrap();
619
620        let mint_close_authority = mint_state
621            .init_extension::<MintCloseAuthority>(true)
622            .unwrap();
623        mint_close_authority.close_authority =
624            OptionalNonZeroPubkey::try_from(Some(owner_pubkey)).unwrap();
625
626        mint_state.base = mint_base;
627        mint_state.pack_base();
628        mint_state.init_account_type().unwrap();
629
630        assert_eq!(
631            parse_token_v3(&mint_data, None).unwrap(),
632            TokenAccountType::Mint(UiMint {
633                mint_authority: Some(owner_pubkey.to_string()),
634                supply: 42.to_string(),
635                decimals: 3,
636                is_initialized: true,
637                freeze_authority: Some(owner_pubkey.to_string()),
638                extensions: vec![UiExtension::MintCloseAuthority(UiMintCloseAuthority {
639                    close_authority: Some(owner_pubkey.to_string()),
640                })],
641            }),
642        );
643    }
644}