Browse Source

rpc: Include interest-bearing configuration for UI amount calculation (#1549)

* Plumb new SplTokenAdditionalData everywhere

* account-decoder: Calculate ui amount with interest

* rpc / ledger: Populate interest bearing information

* rpc: Test interest-bearing config

* Deprecate `parse_token` for `parse_token_v2`

* Deprecate parse_account_data and AccountAdditionalData for v2

* Deprecate token_amount_to_ui_amount -> v2

* Make get_mint_owner_and_additional_data pub(crate)

* ledger: Revert changes, always use raw amount
Jon C 1 year ago
parent
commit
e0dc5dc707

+ 3 - 3
account-decoder/src/lib.rs

@@ -18,7 +18,7 @@ pub mod parse_vote;
 pub mod validator_info;
 pub mod validator_info;
 
 
 use {
 use {
-    crate::parse_account_data::{parse_account_data, AccountAdditionalData, ParsedAccount},
+    crate::parse_account_data::{parse_account_data_v2, AccountAdditionalDataV2, ParsedAccount},
     base64::{prelude::BASE64_STANDARD, Engine},
     base64::{prelude::BASE64_STANDARD, Engine},
     solana_sdk::{
     solana_sdk::{
         account::{ReadableAccount, WritableAccount},
         account::{ReadableAccount, WritableAccount},
@@ -108,7 +108,7 @@ impl UiAccount {
         pubkey: &Pubkey,
         pubkey: &Pubkey,
         account: &T,
         account: &T,
         encoding: UiAccountEncoding,
         encoding: UiAccountEncoding,
-        additional_data: Option<AccountAdditionalData>,
+        additional_data: Option<AccountAdditionalDataV2>,
         data_slice_config: Option<UiDataSliceConfig>,
         data_slice_config: Option<UiDataSliceConfig>,
     ) -> Self {
     ) -> Self {
         let space = account.data().len();
         let space = account.data().len();
@@ -142,7 +142,7 @@ impl UiAccount {
             }
             }
             UiAccountEncoding::JsonParsed => {
             UiAccountEncoding::JsonParsed => {
                 if let Ok(parsed_data) =
                 if let Ok(parsed_data) =
-                    parse_account_data(pubkey, account.owner(), account.data(), additional_data)
+                    parse_account_data_v2(pubkey, account.owner(), account.data(), additional_data)
                 {
                 {
                     UiAccountData::Json(parsed_data)
                     UiAccountData::Json(parsed_data)
                 } else {
                 } else {

+ 51 - 9
account-decoder/src/parse_account_data.rs

@@ -3,14 +3,15 @@ use {
         parse_address_lookup_table::parse_address_lookup_table,
         parse_address_lookup_table::parse_address_lookup_table,
         parse_bpf_loader::parse_bpf_upgradeable_loader, parse_config::parse_config,
         parse_bpf_loader::parse_bpf_upgradeable_loader, parse_config::parse_config,
         parse_nonce::parse_nonce, parse_stake::parse_stake, parse_sysvar::parse_sysvar,
         parse_nonce::parse_nonce, parse_stake::parse_stake, parse_sysvar::parse_sysvar,
-        parse_token::parse_token, parse_vote::parse_vote,
+        parse_token::parse_token_v2, parse_vote::parse_vote,
     },
     },
     inflector::Inflector,
     inflector::Inflector,
     serde_json::Value,
     serde_json::Value,
     solana_sdk::{
     solana_sdk::{
-        address_lookup_table, instruction::InstructionError, pubkey::Pubkey, stake, system_program,
-        sysvar, vote,
+        address_lookup_table, clock::UnixTimestamp, instruction::InstructionError, pubkey::Pubkey,
+        stake, system_program, sysvar, vote,
     },
     },
+    spl_token_2022::extension::interest_bearing_mint::InterestBearingConfig,
     std::collections::HashMap,
     std::collections::HashMap,
     thiserror::Error,
     thiserror::Error,
 };
 };
@@ -84,16 +85,57 @@ pub enum ParsableAccount {
     Vote,
     Vote,
 }
 }
 
 
+#[deprecated(since = "2.0.0", note = "Use `AccountAdditionalDataV2` instead")]
 #[derive(Clone, Copy, Default)]
 #[derive(Clone, Copy, Default)]
 pub struct AccountAdditionalData {
 pub struct AccountAdditionalData {
     pub spl_token_decimals: Option<u8>,
     pub spl_token_decimals: Option<u8>,
 }
 }
 
 
+#[derive(Clone, Copy, Default)]
+pub struct AccountAdditionalDataV2 {
+    pub spl_token_additional_data: Option<SplTokenAdditionalData>,
+}
+
+#[derive(Clone, Copy, Default)]
+pub struct SplTokenAdditionalData {
+    pub decimals: u8,
+    pub interest_bearing_config: Option<(InterestBearingConfig, UnixTimestamp)>,
+}
+
+impl SplTokenAdditionalData {
+    pub fn with_decimals(decimals: u8) -> Self {
+        Self {
+            decimals,
+            ..Default::default()
+        }
+    }
+}
+
+#[deprecated(since = "2.0.0", note = "Use `parse_account_data_v2` instead")]
+#[allow(deprecated)]
 pub fn parse_account_data(
 pub fn parse_account_data(
     pubkey: &Pubkey,
     pubkey: &Pubkey,
     program_id: &Pubkey,
     program_id: &Pubkey,
     data: &[u8],
     data: &[u8],
     additional_data: Option<AccountAdditionalData>,
     additional_data: Option<AccountAdditionalData>,
+) -> Result<ParsedAccount, ParseAccountError> {
+    parse_account_data_v2(
+        pubkey,
+        program_id,
+        data,
+        additional_data.map(|d| AccountAdditionalDataV2 {
+            spl_token_additional_data: d
+                .spl_token_decimals
+                .map(SplTokenAdditionalData::with_decimals),
+        }),
+    )
+}
+
+pub fn parse_account_data_v2(
+    pubkey: &Pubkey,
+    program_id: &Pubkey,
+    data: &[u8],
+    additional_data: Option<AccountAdditionalDataV2>,
 ) -> Result<ParsedAccount, ParseAccountError> {
 ) -> Result<ParsedAccount, ParseAccountError> {
     let program_name = PARSABLE_PROGRAM_IDS
     let program_name = PARSABLE_PROGRAM_IDS
         .get(program_id)
         .get(program_id)
@@ -108,9 +150,9 @@ pub fn parse_account_data(
         }
         }
         ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
         ParsableAccount::Config => serde_json::to_value(parse_config(data, pubkey)?)?,
         ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
         ParsableAccount::Nonce => serde_json::to_value(parse_nonce(data)?)?,
-        ParsableAccount::SplToken | ParsableAccount::SplToken2022 => {
-            serde_json::to_value(parse_token(data, additional_data.spl_token_decimals)?)?
-        }
+        ParsableAccount::SplToken | ParsableAccount::SplToken2022 => serde_json::to_value(
+            parse_token_v2(data, additional_data.spl_token_additional_data.as_ref())?,
+        )?,
         ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
         ParsableAccount::Stake => serde_json::to_value(parse_stake(data)?)?,
         ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
         ParsableAccount::Sysvar => serde_json::to_value(parse_sysvar(data, pubkey)?)?,
         ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
         ParsableAccount::Vote => serde_json::to_value(parse_vote(data)?)?,
@@ -143,13 +185,13 @@ mod test {
         let account_pubkey = solana_sdk::pubkey::new_rand();
         let account_pubkey = solana_sdk::pubkey::new_rand();
         let other_program = solana_sdk::pubkey::new_rand();
         let other_program = solana_sdk::pubkey::new_rand();
         let data = vec![0; 4];
         let data = vec![0; 4];
-        assert!(parse_account_data(&account_pubkey, &other_program, &data, None).is_err());
+        assert!(parse_account_data_v2(&account_pubkey, &other_program, &data, None).is_err());
 
 
         let vote_state = VoteState::default();
         let vote_state = VoteState::default();
         let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
         let mut vote_account_data: Vec<u8> = vec![0; VoteState::size_of()];
         let versioned = VoteStateVersions::new_current(vote_state);
         let versioned = VoteStateVersions::new_current(vote_state);
         VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
         VoteState::serialize(&versioned, &mut vote_account_data).unwrap();
-        let parsed = parse_account_data(
+        let parsed = parse_account_data_v2(
             &account_pubkey,
             &account_pubkey,
             &vote_program_id(),
             &vote_program_id(),
             &vote_account_data,
             &vote_account_data,
@@ -161,7 +203,7 @@ mod test {
 
 
         let nonce_data = Versions::new(State::Initialized(Data::default()));
         let nonce_data = Versions::new(State::Initialized(Data::default()));
         let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
         let nonce_account_data = bincode::serialize(&nonce_data).unwrap();
-        let parsed = parse_account_data(
+        let parsed = parse_account_data_v2(
             &account_pubkey,
             &account_pubkey,
             &system_program::id(),
             &system_program::id(),
             &nonce_account_data,
             &nonce_account_data,

+ 130 - 35
account-decoder/src/parse_token.rs

@@ -1,6 +1,6 @@
 use {
 use {
     crate::{
     crate::{
-        parse_account_data::{ParsableAccount, ParseAccountError},
+        parse_account_data::{ParsableAccount, ParseAccountError, SplTokenAdditionalData},
         parse_token_extension::{parse_extension, UiExtension},
         parse_token_extension::{parse_extension, UiExtension},
         StringAmount, StringDecimals,
         StringAmount, StringDecimals,
     },
     },
@@ -57,12 +57,21 @@ pub fn pubkey_from_spl_token(pubkey: &SplTokenPubkey) -> Pubkey {
     Pubkey::new_from_array(pubkey.to_bytes())
     Pubkey::new_from_array(pubkey.to_bytes())
 }
 }
 
 
+#[deprecated(since = "2.0.0", note = "Use `parse_token_v2` instead")]
 pub fn parse_token(
 pub fn parse_token(
     data: &[u8],
     data: &[u8],
-    mint_decimals: Option<u8>,
+    decimals: Option<u8>,
+) -> Result<TokenAccountType, ParseAccountError> {
+    let additional_data = decimals.map(SplTokenAdditionalData::with_decimals);
+    parse_token_v2(data, additional_data.as_ref())
+}
+
+pub fn parse_token_v2(
+    data: &[u8],
+    additional_data: Option<&SplTokenAdditionalData>,
 ) -> Result<TokenAccountType, ParseAccountError> {
 ) -> Result<TokenAccountType, ParseAccountError> {
     if let Ok(account) = StateWithExtensions::<Account>::unpack(data) {
     if let Ok(account) = StateWithExtensions::<Account>::unpack(data) {
-        let decimals = mint_decimals.ok_or_else(|| {
+        let additional_data = additional_data.as_ref().ok_or_else(|| {
             ParseAccountError::AdditionalDataMissing(
             ParseAccountError::AdditionalDataMissing(
                 "no mint_decimals provided to parse spl-token account".to_string(),
                 "no mint_decimals provided to parse spl-token account".to_string(),
             )
             )
@@ -75,7 +84,7 @@ pub fn parse_token(
         return Ok(TokenAccountType::Account(UiTokenAccount {
         return Ok(TokenAccountType::Account(UiTokenAccount {
             mint: account.base.mint.to_string(),
             mint: account.base.mint.to_string(),
             owner: account.base.owner.to_string(),
             owner: account.base.owner.to_string(),
-            token_amount: token_amount_to_ui_amount(account.base.amount, decimals),
+            token_amount: token_amount_to_ui_amount_v2(account.base.amount, additional_data),
             delegate: match account.base.delegate {
             delegate: match account.base.delegate {
                 COption::Some(pubkey) => Some(pubkey.to_string()),
                 COption::Some(pubkey) => Some(pubkey.to_string()),
                 COption::None => None,
                 COption::None => None,
@@ -83,15 +92,17 @@ pub fn parse_token(
             state: account.base.state.into(),
             state: account.base.state.into(),
             is_native: account.base.is_native(),
             is_native: account.base.is_native(),
             rent_exempt_reserve: match account.base.is_native {
             rent_exempt_reserve: match account.base.is_native {
-                COption::Some(reserve) => Some(token_amount_to_ui_amount(reserve, decimals)),
+                COption::Some(reserve) => {
+                    Some(token_amount_to_ui_amount_v2(reserve, additional_data))
+                }
                 COption::None => None,
                 COption::None => None,
             },
             },
             delegated_amount: if account.base.delegate.is_none() {
             delegated_amount: if account.base.delegate.is_none() {
                 None
                 None
             } else {
             } else {
-                Some(token_amount_to_ui_amount(
+                Some(token_amount_to_ui_amount_v2(
                     account.base.delegated_amount,
                     account.base.delegated_amount,
-                    decimals,
+                    additional_data,
                 ))
                 ))
             },
             },
             close_authority: match account.base.close_authority {
             close_authority: match account.base.close_authority {
@@ -246,15 +257,38 @@ impl UiTokenAmount {
     }
     }
 }
 }
 
 
+#[deprecated(since = "2.0.0", note = "Use `token_amount_to_ui_amount_v2` instead")]
 pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
 pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount {
-    let amount_decimals = 10_usize
-        .checked_pow(decimals as u32)
-        .map(|dividend| amount as f64 / dividend as f64);
+    token_amount_to_ui_amount_v2(amount, &SplTokenAdditionalData::with_decimals(decimals))
+}
+
+pub fn token_amount_to_ui_amount_v2(
+    amount: u64,
+    additional_data: &SplTokenAdditionalData,
+) -> UiTokenAmount {
+    let decimals = additional_data.decimals;
+    let (ui_amount, ui_amount_string) = if let Some((interest_bearing_config, unix_timestamp)) =
+        additional_data.interest_bearing_config
+    {
+        let ui_amount_string =
+            interest_bearing_config.amount_to_ui_amount(amount, decimals, unix_timestamp);
+        (
+            ui_amount_string
+                .as_ref()
+                .and_then(|x| f64::from_str(x).ok()),
+            ui_amount_string.unwrap_or("".to_string()),
+        )
+    } else {
+        let ui_amount = 10_usize
+            .checked_pow(decimals as u32)
+            .map(|dividend| amount as f64 / dividend as f64);
+        (ui_amount, real_number_string_trimmed(amount, decimals))
+    };
     UiTokenAmount {
     UiTokenAmount {
-        ui_amount: amount_decimals,
+        ui_amount,
         decimals,
         decimals,
         amount: amount.to_string(),
         amount: amount.to_string(),
-        ui_amount_string: real_number_string_trimmed(amount, decimals),
+        ui_amount_string,
     }
     }
 }
 }
 
 
@@ -292,12 +326,14 @@ mod test {
         crate::parse_token_extension::{UiMemoTransfer, UiMintCloseAuthority},
         crate::parse_token_extension::{UiMemoTransfer, UiMintCloseAuthority},
         spl_pod::optional_keys::OptionalNonZeroPubkey,
         spl_pod::optional_keys::OptionalNonZeroPubkey,
         spl_token_2022::extension::{
         spl_token_2022::extension::{
-            immutable_owner::ImmutableOwner, memo_transfer::MemoTransfer,
-            mint_close_authority::MintCloseAuthority, BaseStateWithExtensionsMut, ExtensionType,
-            StateWithExtensionsMut,
+            immutable_owner::ImmutableOwner, interest_bearing_mint::InterestBearingConfig,
+            memo_transfer::MemoTransfer, mint_close_authority::MintCloseAuthority,
+            BaseStateWithExtensionsMut, ExtensionType, StateWithExtensionsMut,
         },
         },
     };
     };
 
 
+    const INT_SECONDS_PER_YEAR: i64 = 6 * 6 * 24 * 36524;
+
     #[test]
     #[test]
     fn test_parse_token() {
     fn test_parse_token() {
         let mint_pubkey = SplTokenPubkey::new_from_array([2; 32]);
         let mint_pubkey = SplTokenPubkey::new_from_array([2; 32]);
@@ -312,9 +348,13 @@ mod test {
         account.close_authority = COption::Some(owner_pubkey);
         account.close_authority = COption::Some(owner_pubkey);
         Account::pack(account, &mut account_data).unwrap();
         Account::pack(account, &mut account_data).unwrap();
 
 
-        assert!(parse_token(&account_data, None).is_err());
+        assert!(parse_token_v2(&account_data, None).is_err());
         assert_eq!(
         assert_eq!(
-            parse_token(&account_data, Some(2)).unwrap(),
+            parse_token_v2(
+                &account_data,
+                Some(&SplTokenAdditionalData::with_decimals(2))
+            )
+            .unwrap(),
             TokenAccountType::Account(UiTokenAccount {
             TokenAccountType::Account(UiTokenAccount {
                 mint: mint_pubkey.to_string(),
                 mint: mint_pubkey.to_string(),
                 owner: owner_pubkey.to_string(),
                 owner: owner_pubkey.to_string(),
@@ -344,7 +384,7 @@ mod test {
         Mint::pack(mint, &mut mint_data).unwrap();
         Mint::pack(mint, &mut mint_data).unwrap();
 
 
         assert_eq!(
         assert_eq!(
-            parse_token(&mint_data, None).unwrap(),
+            parse_token_v2(&mint_data, None).unwrap(),
             TokenAccountType::Mint(UiMint {
             TokenAccountType::Mint(UiMint {
                 mint_authority: Some(owner_pubkey.to_string()),
                 mint_authority: Some(owner_pubkey.to_string()),
                 supply: 42.to_string(),
                 supply: 42.to_string(),
@@ -371,7 +411,7 @@ mod test {
         Multisig::pack(multisig, &mut multisig_data).unwrap();
         Multisig::pack(multisig, &mut multisig_data).unwrap();
 
 
         assert_eq!(
         assert_eq!(
-            parse_token(&multisig_data, None).unwrap(),
+            parse_token_v2(&multisig_data, None).unwrap(),
             TokenAccountType::Multisig(UiMultisig {
             TokenAccountType::Multisig(UiMultisig {
                 num_required_signers: 2,
                 num_required_signers: 2,
                 num_valid_signers: 3,
                 num_valid_signers: 3,
@@ -385,7 +425,7 @@ mod test {
         );
         );
 
 
         let bad_data = vec![0; 4];
         let bad_data = vec![0; 4];
-        assert!(parse_token(&bad_data, None).is_err());
+        assert!(parse_token_v2(&bad_data, None).is_err());
     }
     }
 
 
     #[test]
     #[test]
@@ -408,7 +448,8 @@ mod test {
     fn test_ui_token_amount_real_string() {
     fn test_ui_token_amount_real_string() {
         assert_eq!(&real_number_string(1, 0), "1");
         assert_eq!(&real_number_string(1, 0), "1");
         assert_eq!(&real_number_string_trimmed(1, 0), "1");
         assert_eq!(&real_number_string_trimmed(1, 0), "1");
-        let token_amount = token_amount_to_ui_amount(1, 0);
+        let token_amount =
+            token_amount_to_ui_amount_v2(1, &SplTokenAdditionalData::with_decimals(0));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(1, 0)
             real_number_string_trimmed(1, 0)
@@ -416,7 +457,8 @@ mod test {
         assert_eq!(token_amount.ui_amount, Some(1.0));
         assert_eq!(token_amount.ui_amount, Some(1.0));
         assert_eq!(&real_number_string(10, 0), "10");
         assert_eq!(&real_number_string(10, 0), "10");
         assert_eq!(&real_number_string_trimmed(10, 0), "10");
         assert_eq!(&real_number_string_trimmed(10, 0), "10");
-        let token_amount = token_amount_to_ui_amount(10, 0);
+        let token_amount =
+            token_amount_to_ui_amount_v2(10, &SplTokenAdditionalData::with_decimals(0));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(10, 0)
             real_number_string_trimmed(10, 0)
@@ -424,7 +466,8 @@ mod test {
         assert_eq!(token_amount.ui_amount, Some(10.0));
         assert_eq!(token_amount.ui_amount, Some(10.0));
         assert_eq!(&real_number_string(1, 9), "0.000000001");
         assert_eq!(&real_number_string(1, 9), "0.000000001");
         assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
         assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001");
-        let token_amount = token_amount_to_ui_amount(1, 9);
+        let token_amount =
+            token_amount_to_ui_amount_v2(1, &SplTokenAdditionalData::with_decimals(9));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(1, 9)
             real_number_string_trimmed(1, 9)
@@ -432,7 +475,8 @@ mod test {
         assert_eq!(token_amount.ui_amount, Some(0.000000001));
         assert_eq!(token_amount.ui_amount, Some(0.000000001));
         assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
         assert_eq!(&real_number_string(1_000_000_000, 9), "1.000000000");
         assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
         assert_eq!(&real_number_string_trimmed(1_000_000_000, 9), "1");
-        let token_amount = token_amount_to_ui_amount(1_000_000_000, 9);
+        let token_amount =
+            token_amount_to_ui_amount_v2(1_000_000_000, &SplTokenAdditionalData::with_decimals(9));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(1_000_000_000, 9)
             real_number_string_trimmed(1_000_000_000, 9)
@@ -440,7 +484,8 @@ mod test {
         assert_eq!(token_amount.ui_amount, Some(1.0));
         assert_eq!(token_amount.ui_amount, Some(1.0));
         assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
         assert_eq!(&real_number_string(1_234_567_890, 3), "1234567.890");
         assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
         assert_eq!(&real_number_string_trimmed(1_234_567_890, 3), "1234567.89");
-        let token_amount = token_amount_to_ui_amount(1_234_567_890, 3);
+        let token_amount =
+            token_amount_to_ui_amount_v2(1_234_567_890, &SplTokenAdditionalData::with_decimals(3));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(1_234_567_890, 3)
             real_number_string_trimmed(1_234_567_890, 3)
@@ -454,7 +499,8 @@ mod test {
             &real_number_string_trimmed(1_234_567_890, 25),
             &real_number_string_trimmed(1_234_567_890, 25),
             "0.000000000000000123456789"
             "0.000000000000000123456789"
         );
         );
-        let token_amount = token_amount_to_ui_amount(1_234_567_890, 20);
+        let token_amount =
+            token_amount_to_ui_amount_v2(1_234_567_890, &SplTokenAdditionalData::with_decimals(20));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(1_234_567_890, 20)
             real_number_string_trimmed(1_234_567_890, 20)
@@ -462,11 +508,50 @@ mod test {
         assert_eq!(token_amount.ui_amount, None);
         assert_eq!(token_amount.ui_amount, None);
     }
     }
 
 
+    #[test]
+    fn test_ui_token_amount_with_interest() {
+        // constant 5%
+        let config = InterestBearingConfig {
+            initialization_timestamp: 0.into(),
+            pre_update_average_rate: 500.into(),
+            last_update_timestamp: INT_SECONDS_PER_YEAR.into(),
+            current_rate: 500.into(),
+            ..Default::default()
+        };
+        let additional_data = SplTokenAdditionalData {
+            decimals: 0,
+            interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR)),
+        };
+        let token_amount = token_amount_to_ui_amount_v2(1, &additional_data);
+        assert_eq!(token_amount.ui_amount_string, "1.0512710963760241");
+        assert!((token_amount.ui_amount.unwrap() - 1.0512710963760241f64).abs() < f64::EPSILON);
+        let token_amount = token_amount_to_ui_amount_v2(10, &additional_data);
+        assert_eq!(token_amount.ui_amount_string, "10.512710963760242");
+        assert!((token_amount.ui_amount.unwrap() - 10.512710963760241f64).abs() < f64::EPSILON);
+
+        // huge case
+        let config = InterestBearingConfig {
+            initialization_timestamp: 0.into(),
+            pre_update_average_rate: 32767.into(),
+            last_update_timestamp: 0.into(),
+            current_rate: 32767.into(),
+            ..Default::default()
+        };
+        let additional_data = SplTokenAdditionalData {
+            decimals: 0,
+            interest_bearing_config: Some((config, INT_SECONDS_PER_YEAR * 1_000)),
+        };
+        let token_amount = token_amount_to_ui_amount_v2(u64::MAX, &additional_data);
+        assert_eq!(token_amount.ui_amount, Some(f64::INFINITY));
+        assert_eq!(token_amount.ui_amount_string, "inf");
+    }
+
     #[test]
     #[test]
     fn test_ui_token_amount_real_string_zero() {
     fn test_ui_token_amount_real_string_zero() {
         assert_eq!(&real_number_string(0, 0), "0");
         assert_eq!(&real_number_string(0, 0), "0");
         assert_eq!(&real_number_string_trimmed(0, 0), "0");
         assert_eq!(&real_number_string_trimmed(0, 0), "0");
-        let token_amount = token_amount_to_ui_amount(0, 0);
+        let token_amount =
+            token_amount_to_ui_amount_v2(0, &SplTokenAdditionalData::with_decimals(0));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(0, 0)
             real_number_string_trimmed(0, 0)
@@ -474,7 +559,8 @@ mod test {
         assert_eq!(token_amount.ui_amount, Some(0.0));
         assert_eq!(token_amount.ui_amount, Some(0.0));
         assert_eq!(&real_number_string(0, 9), "0.000000000");
         assert_eq!(&real_number_string(0, 9), "0.000000000");
         assert_eq!(&real_number_string_trimmed(0, 9), "0");
         assert_eq!(&real_number_string_trimmed(0, 9), "0");
-        let token_amount = token_amount_to_ui_amount(0, 9);
+        let token_amount =
+            token_amount_to_ui_amount_v2(0, &SplTokenAdditionalData::with_decimals(9));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(0, 9)
             real_number_string_trimmed(0, 9)
@@ -482,7 +568,8 @@ mod test {
         assert_eq!(token_amount.ui_amount, Some(0.0));
         assert_eq!(token_amount.ui_amount, Some(0.0));
         assert_eq!(&real_number_string(0, 25), "0.0000000000000000000000000");
         assert_eq!(&real_number_string(0, 25), "0.0000000000000000000000000");
         assert_eq!(&real_number_string_trimmed(0, 25), "0");
         assert_eq!(&real_number_string_trimmed(0, 25), "0");
-        let token_amount = token_amount_to_ui_amount(0, 20);
+        let token_amount =
+            token_amount_to_ui_amount_v2(0, &SplTokenAdditionalData::with_decimals(20));
         assert_eq!(
         assert_eq!(
             token_amount.ui_amount_string,
             token_amount.ui_amount_string,
             real_number_string_trimmed(0, 20)
             real_number_string_trimmed(0, 20)
@@ -518,9 +605,13 @@ mod test {
         account_state.pack_base();
         account_state.pack_base();
         account_state.init_account_type().unwrap();
         account_state.init_account_type().unwrap();
 
 
-        assert!(parse_token(&account_data, None).is_err());
+        assert!(parse_token_v2(&account_data, None).is_err());
         assert_eq!(
         assert_eq!(
-            parse_token(&account_data, Some(2)).unwrap(),
+            parse_token_v2(
+                &account_data,
+                Some(&SplTokenAdditionalData::with_decimals(2))
+            )
+            .unwrap(),
             TokenAccountType::Account(UiTokenAccount {
             TokenAccountType::Account(UiTokenAccount {
                 mint: mint_pubkey.to_string(),
                 mint: mint_pubkey.to_string(),
                 owner: owner_pubkey.to_string(),
                 owner: owner_pubkey.to_string(),
@@ -554,9 +645,13 @@ mod test {
         let memo_transfer = account_state.init_extension::<MemoTransfer>(true).unwrap();
         let memo_transfer = account_state.init_extension::<MemoTransfer>(true).unwrap();
         memo_transfer.require_incoming_transfer_memos = true.into();
         memo_transfer.require_incoming_transfer_memos = true.into();
 
 
-        assert!(parse_token(&account_data, None).is_err());
+        assert!(parse_token_v2(&account_data, None).is_err());
         assert_eq!(
         assert_eq!(
-            parse_token(&account_data, Some(2)).unwrap(),
+            parse_token_v2(
+                &account_data,
+                Some(&SplTokenAdditionalData::with_decimals(2))
+            )
+            .unwrap(),
             TokenAccountType::Account(UiTokenAccount {
             TokenAccountType::Account(UiTokenAccount {
                 mint: mint_pubkey.to_string(),
                 mint: mint_pubkey.to_string(),
                 owner: owner_pubkey.to_string(),
                 owner: owner_pubkey.to_string(),
@@ -604,7 +699,7 @@ mod test {
         mint_state.init_account_type().unwrap();
         mint_state.init_account_type().unwrap();
 
 
         assert_eq!(
         assert_eq!(
-            parse_token(&mint_data, None).unwrap(),
+            parse_token_v2(&mint_data, None).unwrap(),
             TokenAccountType::Mint(UiMint {
             TokenAccountType::Mint(UiMint {
                 mint_authority: Some(owner_pubkey.to_string()),
                 mint_authority: Some(owner_pubkey.to_string()),
                 supply: 42.to_string(),
                 supply: 42.to_string(),
@@ -630,7 +725,7 @@ mod test {
         mint_state.init_account_type().unwrap();
         mint_state.init_account_type().unwrap();
 
 
         assert_eq!(
         assert_eq!(
-            parse_token(&mint_data, None).unwrap(),
+            parse_token_v2(&mint_data, None).unwrap(),
             TokenAccountType::Mint(UiMint {
             TokenAccountType::Mint(UiMint {
                 mint_authority: Some(owner_pubkey.to_string()),
                 mint_authority: Some(owner_pubkey.to_string()),
                 supply: 42.to_string(),
                 supply: 42.to_string(),

+ 2 - 2
cli-output/src/cli_output.rs

@@ -17,7 +17,7 @@ use {
     serde::{Deserialize, Serialize},
     serde::{Deserialize, Serialize},
     serde_json::{Map, Value},
     serde_json::{Map, Value},
     solana_account_decoder::{
     solana_account_decoder::{
-        parse_account_data::AccountAdditionalData, parse_token::UiTokenAccount, UiAccount,
+        parse_account_data::AccountAdditionalDataV2, parse_token::UiTokenAccount, UiAccount,
         UiAccountEncoding, UiDataSliceConfig,
         UiAccountEncoding, UiDataSliceConfig,
     },
     },
     solana_clap_utils::keypair::SignOnly,
     solana_clap_utils::keypair::SignOnly,
@@ -159,7 +159,7 @@ pub struct CliAccount {
 
 
 pub struct CliAccountNewConfig {
 pub struct CliAccountNewConfig {
     pub data_encoding: UiAccountEncoding,
     pub data_encoding: UiAccountEncoding,
-    pub additional_data: Option<AccountAdditionalData>,
+    pub additional_data: Option<AccountAdditionalDataV2>,
     pub data_slice_config: Option<UiDataSliceConfig>,
     pub data_slice_config: Option<UiDataSliceConfig>,
     pub use_lamports_unit: bool,
     pub use_lamports_unit: bool,
 }
 }

+ 10 - 3
ledger/src/token_balances.rs

@@ -1,6 +1,7 @@
 use {
 use {
-    solana_account_decoder::parse_token::{
-        is_known_spl_token_id, token_amount_to_ui_amount, UiTokenAmount,
+    solana_account_decoder::{
+        parse_account_data::SplTokenAdditionalData,
+        parse_token::{is_known_spl_token_id, token_amount_to_ui_amount_v2, UiTokenAmount},
     },
     },
     solana_measure::measure::Measure,
     solana_measure::measure::Measure,
     solana_metrics::datapoint_debug,
     solana_metrics::datapoint_debug,
@@ -111,7 +112,13 @@ fn collect_token_balance_from_account(
     Some(TokenBalanceData {
     Some(TokenBalanceData {
         mint: token_account.base.mint.to_string(),
         mint: token_account.base.mint.to_string(),
         owner: token_account.base.owner.to_string(),
         owner: token_account.base.owner.to_string(),
-        ui_token_amount: token_amount_to_ui_amount(token_account.base.amount, decimals),
+        ui_token_amount: token_amount_to_ui_amount_v2(
+            token_account.base.amount,
+            // NOTE: Same as parsed instruction data, ledger data always uses
+            // the raw token amount, and does not calculate the UI amount with
+            // any consideration for interest.
+            &SplTokenAdditionalData::with_decimals(decimals),
+        ),
         program_id: account.owner().to_string(),
         program_id: account.owner().to_string(),
     })
     })
 }
 }

+ 44 - 19
rpc/src/parsed_token_accounts.rs

@@ -2,8 +2,9 @@ use {
     crate::rpc::account_resolver,
     crate::rpc::account_resolver,
     jsonrpc_core::{Error, Result},
     jsonrpc_core::{Error, Result},
     solana_account_decoder::{
     solana_account_decoder::{
-        parse_account_data::AccountAdditionalData, parse_token::get_token_account_mint, UiAccount,
-        UiAccountData, UiAccountEncoding,
+        parse_account_data::{AccountAdditionalDataV2, SplTokenAdditionalData},
+        parse_token::get_token_account_mint,
+        UiAccount, UiAccountData, UiAccountEncoding,
     },
     },
     solana_rpc_client_api::response::RpcKeyedAccount,
     solana_rpc_client_api::response::RpcKeyedAccount,
     solana_runtime::bank::Bank,
     solana_runtime::bank::Bank,
@@ -11,7 +12,13 @@ use {
         account::{AccountSharedData, ReadableAccount},
         account::{AccountSharedData, ReadableAccount},
         pubkey::Pubkey,
         pubkey::Pubkey,
     },
     },
-    spl_token_2022::{extension::StateWithExtensions, state::Mint},
+    spl_token_2022::{
+        extension::{
+            interest_bearing_mint::InterestBearingConfig, BaseStateWithExtensions,
+            StateWithExtensions,
+        },
+        state::Mint,
+    },
     std::{collections::HashMap, sync::Arc},
     std::{collections::HashMap, sync::Arc},
 };
 };
 
 
@@ -30,8 +37,9 @@ pub fn get_parsed_token_account(
                 overwrite_accounts,
                 overwrite_accounts,
             )
             )
         })
         })
-        .map(|mint_account| AccountAdditionalData {
-            spl_token_decimals: get_mint_decimals(mint_account.data()).ok(),
+        .and_then(|mint_account| get_additional_mint_data(bank, mint_account.data()).ok())
+        .map(|data| AccountAdditionalDataV2 {
+            spl_token_additional_data: Some(data),
         });
         });
 
 
     UiAccount::encode(
     UiAccount::encode(
@@ -50,15 +58,17 @@ pub fn get_parsed_token_accounts<I>(
 where
 where
     I: Iterator<Item = (Pubkey, AccountSharedData)>,
     I: Iterator<Item = (Pubkey, AccountSharedData)>,
 {
 {
-    let mut mint_decimals: HashMap<Pubkey, u8> = HashMap::new();
+    let mut mint_data: HashMap<Pubkey, AccountAdditionalDataV2> = HashMap::new();
     keyed_accounts.filter_map(move |(pubkey, account)| {
     keyed_accounts.filter_map(move |(pubkey, account)| {
-        let additional_data = get_token_account_mint(account.data()).map(|mint_pubkey| {
-            let spl_token_decimals = mint_decimals.get(&mint_pubkey).cloned().or_else(|| {
-                let (_, decimals) = get_mint_owner_and_decimals(&bank, &mint_pubkey).ok()?;
-                mint_decimals.insert(mint_pubkey, decimals);
-                Some(decimals)
-            });
-            AccountAdditionalData { spl_token_decimals }
+        let additional_data = get_token_account_mint(account.data()).and_then(|mint_pubkey| {
+            mint_data.get(&mint_pubkey).cloned().or_else(|| {
+                let (_, data) = get_mint_owner_and_additional_data(&bank, &mint_pubkey).ok()?;
+                let data = AccountAdditionalDataV2 {
+                    spl_token_additional_data: Some(data),
+                };
+                mint_data.insert(mint_pubkey, data);
+                Some(data)
+            })
         });
         });
 
 
         let maybe_encoded_account = UiAccount::encode(
         let maybe_encoded_account = UiAccount::encode(
@@ -81,22 +91,37 @@ where
 
 
 /// Analyze a mint Pubkey that may be the native_mint and get the mint-account owner (token
 /// Analyze a mint Pubkey that may be the native_mint and get the mint-account owner (token
 /// program_id) and decimals
 /// program_id) and decimals
-pub fn get_mint_owner_and_decimals(bank: &Bank, mint: &Pubkey) -> Result<(Pubkey, u8)> {
+pub(crate) fn get_mint_owner_and_additional_data(
+    bank: &Bank,
+    mint: &Pubkey,
+) -> Result<(Pubkey, SplTokenAdditionalData)> {
     if mint == &spl_token::native_mint::id() {
     if mint == &spl_token::native_mint::id() {
-        Ok((spl_token::id(), spl_token::native_mint::DECIMALS))
+        Ok((
+            spl_token::id(),
+            SplTokenAdditionalData::with_decimals(spl_token::native_mint::DECIMALS),
+        ))
     } else {
     } else {
         let mint_account = bank.get_account(mint).ok_or_else(|| {
         let mint_account = bank.get_account(mint).ok_or_else(|| {
             Error::invalid_params("Invalid param: could not find mint".to_string())
             Error::invalid_params("Invalid param: could not find mint".to_string())
         })?;
         })?;
-        let decimals = get_mint_decimals(mint_account.data())?;
-        Ok((*mint_account.owner(), decimals))
+        let mint_data = get_additional_mint_data(bank, mint_account.data())?;
+        Ok((*mint_account.owner(), mint_data))
     }
     }
 }
 }
 
 
-fn get_mint_decimals(data: &[u8]) -> Result<u8> {
+fn get_additional_mint_data(bank: &Bank, data: &[u8]) -> Result<SplTokenAdditionalData> {
     StateWithExtensions::<Mint>::unpack(data)
     StateWithExtensions::<Mint>::unpack(data)
         .map_err(|_| {
         .map_err(|_| {
             Error::invalid_params("Invalid param: Token mint could not be unpacked".to_string())
             Error::invalid_params("Invalid param: Token mint could not be unpacked".to_string())
         })
         })
-        .map(|mint| mint.base.decimals)
+        .map(|mint| {
+            let interest_bearing_config = mint
+                .get_extension::<InterestBearingConfig>()
+                .map(|x| (*x, bank.clock().unix_timestamp))
+                .ok();
+            SplTokenAdditionalData {
+                decimals: mint.base.decimals,
+                interest_bearing_config,
+            }
+        })
 }
 }

+ 85 - 42
rpc/src/rpc.rs

@@ -11,7 +11,8 @@ use {
     jsonrpc_core::{futures::future, types::error, BoxFuture, Error, Metadata, Result},
     jsonrpc_core::{futures::future, types::error, BoxFuture, Error, Metadata, Result},
     jsonrpc_derive::rpc,
     jsonrpc_derive::rpc,
     solana_account_decoder::{
     solana_account_decoder::{
-        parse_token::{is_known_spl_token_id, token_amount_to_ui_amount, UiTokenAmount},
+        parse_account_data::SplTokenAdditionalData,
+        parse_token::{is_known_spl_token_id, token_amount_to_ui_amount_v2, UiTokenAmount},
         UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES,
         UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES,
     },
     },
     solana_accounts_db::{
     solana_accounts_db::{
@@ -97,7 +98,10 @@ use {
     },
     },
     solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY},
     solana_vote_program::vote_state::{VoteState, MAX_LOCKOUT_HISTORY},
     spl_token_2022::{
     spl_token_2022::{
-        extension::StateWithExtensions,
+        extension::{
+            interest_bearing_mint::InterestBearingConfig, BaseStateWithExtensions,
+            StateWithExtensions,
+        },
         solana_program::program_pack::Pack,
         solana_program::program_pack::Pack,
         state::{Account as TokenAccount, Mint},
         state::{Account as TokenAccount, Mint},
     },
     },
@@ -1866,8 +1870,8 @@ impl JsonRpcRequestProcessor {
             .map_err(|_| Error::invalid_params("Invalid param: not a Token account".to_string()))?;
             .map_err(|_| Error::invalid_params("Invalid param: not a Token account".to_string()))?;
         let mint = &Pubkey::from_str(&token_account.base.mint.to_string())
         let mint = &Pubkey::from_str(&token_account.base.mint.to_string())
             .expect("Token account mint should be convertible to Pubkey");
             .expect("Token account mint should be convertible to Pubkey");
-        let (_, decimals) = get_mint_owner_and_decimals(&bank, mint)?;
-        let balance = token_amount_to_ui_amount(token_account.base.amount, decimals);
+        let (_, data) = get_mint_owner_and_additional_data(&bank, mint)?;
+        let balance = token_amount_to_ui_amount_v2(token_account.base.amount, &data);
         Ok(new_response(&bank, balance))
         Ok(new_response(&bank, balance))
     }
     }
 
 
@@ -1889,7 +1893,18 @@ impl JsonRpcRequestProcessor {
             Error::invalid_params("Invalid param: mint could not be unpacked".to_string())
             Error::invalid_params("Invalid param: mint could not be unpacked".to_string())
         })?;
         })?;
 
 
-        let supply = token_amount_to_ui_amount(mint.base.supply, mint.base.decimals);
+        let interest_bearing_config = mint
+            .get_extension::<InterestBearingConfig>()
+            .map(|x| (*x, bank.clock().unix_timestamp))
+            .ok();
+
+        let supply = token_amount_to_ui_amount_v2(
+            mint.base.supply,
+            &SplTokenAdditionalData {
+                decimals: mint.base.decimals,
+                interest_bearing_config,
+            },
+        );
         Ok(new_response(&bank, supply))
         Ok(new_response(&bank, supply))
     }
     }
 
 
@@ -1899,7 +1914,7 @@ impl JsonRpcRequestProcessor {
         commitment: Option<CommitmentConfig>,
         commitment: Option<CommitmentConfig>,
     ) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>> {
     ) -> Result<RpcResponse<Vec<RpcTokenAccountBalance>>> {
         let bank = self.bank(commitment);
         let bank = self.bank(commitment);
-        let (mint_owner, decimals) = get_mint_owner_and_decimals(&bank, mint)?;
+        let (mint_owner, data) = get_mint_owner_and_additional_data(&bank, mint)?;
         if !is_known_spl_token_id(&mint_owner) {
         if !is_known_spl_token_id(&mint_owner) {
             return Err(Error::invalid_params(
             return Err(Error::invalid_params(
                 "Invalid param: not a Token mint".to_string(),
                 "Invalid param: not a Token mint".to_string(),
@@ -1931,11 +1946,13 @@ impl JsonRpcRequestProcessor {
         let token_balances = token_balances
         let token_balances = token_balances
             .into_sorted_vec()
             .into_sorted_vec()
             .into_iter()
             .into_iter()
-            .map(|Reverse((amount, address))| RpcTokenAccountBalance {
-                address: address.to_string(),
-                amount: token_amount_to_ui_amount(amount, decimals),
+            .map(|Reverse((amount, address))| {
+                Ok(RpcTokenAccountBalance {
+                    address: address.to_string(),
+                    amount: token_amount_to_ui_amount_v2(amount, &data),
+                })
             })
             })
-            .collect();
+            .collect::<Result<Vec<_>>>()?;
 
 
         Ok(new_response(&bank, token_balances))
         Ok(new_response(&bank, token_balances))
     }
     }
@@ -2525,7 +2542,7 @@ fn get_token_program_id_and_mint(
 ) -> Result<(Pubkey, Option<Pubkey>)> {
 ) -> Result<(Pubkey, Option<Pubkey>)> {
     match token_account_filter {
     match token_account_filter {
         TokenAccountsFilter::Mint(mint) => {
         TokenAccountsFilter::Mint(mint) => {
-            let (mint_owner, _) = get_mint_owner_and_decimals(bank, &mint)?;
+            let (mint_owner, _) = get_mint_owner_and_additional_data(bank, &mint)?;
             if !is_known_spl_token_id(&mint_owner) {
             if !is_known_spl_token_id(&mint_owner) {
                 return Err(Error::invalid_params(
                 return Err(Error::invalid_params(
                     "Invalid param: not a Token mint".to_string(),
                     "Invalid param: not a Token mint".to_string(),
@@ -8546,17 +8563,22 @@ pub mod tests {
             let owner = SplTokenPubkey::new_from_array([3; 32]);
             let owner = SplTokenPubkey::new_from_array([3; 32]);
             let delegate = SplTokenPubkey::new_from_array([4; 32]);
             let delegate = SplTokenPubkey::new_from_array([4; 32]);
             let token_account_pubkey = solana_sdk::pubkey::new_rand();
             let token_account_pubkey = solana_sdk::pubkey::new_rand();
-            let (program_name, account_size, mint_size) = if program_id
+            let amount = 420;
+            let delegated_amount = 30;
+            let rent_exempt_amount = 10;
+            let supply = 500;
+            let decimals = 2;
+            let (program_name, account_size, mint_size, additional_data) = if program_id
                 == solana_inline_spl::token_2022::id()
                 == solana_inline_spl::token_2022::id()
             {
             {
                 let account_base = TokenAccount {
                 let account_base = TokenAccount {
                     mint,
                     mint,
                     owner,
                     owner,
                     delegate: COption::Some(delegate),
                     delegate: COption::Some(delegate),
-                    amount: 420,
+                    amount,
                     state: TokenAccountState::Initialized,
                     state: TokenAccountState::Initialized,
-                    is_native: COption::Some(10),
-                    delegated_amount: 30,
+                    is_native: COption::Some(rent_exempt_amount),
+                    delegated_amount,
                     close_authority: COption::Some(owner),
                     close_authority: COption::Some(owner),
                 };
                 };
                 let account_size = ExtensionType::try_calculate_account_len::<TokenAccount>(&[
                 let account_size = ExtensionType::try_calculate_account_len::<TokenAccount>(&[
@@ -8588,12 +8610,13 @@ pub mod tests {
 
 
                 let mint_size = ExtensionType::try_calculate_account_len::<Mint>(&[
                 let mint_size = ExtensionType::try_calculate_account_len::<Mint>(&[
                     ExtensionType::MintCloseAuthority,
                     ExtensionType::MintCloseAuthority,
+                    ExtensionType::InterestBearingConfig,
                 ])
                 ])
                 .unwrap();
                 .unwrap();
                 let mint_base = Mint {
                 let mint_base = Mint {
                     mint_authority: COption::Some(owner),
                     mint_authority: COption::Some(owner),
-                    supply: 500,
-                    decimals: 2,
+                    supply,
+                    decimals,
                     is_initialized: true,
                     is_initialized: true,
                     freeze_authority: COption::Some(owner),
                     freeze_authority: COption::Some(owner),
                 };
                 };
@@ -8609,6 +8632,22 @@ pub mod tests {
                     .unwrap();
                     .unwrap();
                 mint_close_authority.close_authority =
                 mint_close_authority.close_authority =
                     OptionalNonZeroPubkey::try_from(Some(owner)).unwrap();
                     OptionalNonZeroPubkey::try_from(Some(owner)).unwrap();
+                let interest_bearing_config = mint_state
+                    .init_extension::<InterestBearingConfig>(true)
+                    .unwrap();
+                interest_bearing_config.initialization_timestamp =
+                    bank.clock().unix_timestamp.saturating_sub(1_000_000).into();
+                interest_bearing_config.pre_update_average_rate = 500.into();
+                interest_bearing_config.last_update_timestamp = bank.clock().unix_timestamp.into();
+                interest_bearing_config.current_rate = 500.into();
+
+                let additional_data = SplTokenAdditionalData {
+                    decimals,
+                    interest_bearing_config: Some((
+                        *interest_bearing_config,
+                        bank.clock().unix_timestamp,
+                    )),
+                };
 
 
                 let mint_account = AccountSharedData::from(Account {
                 let mint_account = AccountSharedData::from(Account {
                     lamports: 111,
                     lamports: 111,
@@ -8617,7 +8656,7 @@ pub mod tests {
                     ..Account::default()
                     ..Account::default()
                 });
                 });
                 bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
                 bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
-                ("spl-token-2022", account_size, mint_size)
+                ("spl-token-2022", account_size, mint_size, additional_data)
             } else {
             } else {
                 let account_size = TokenAccount::get_packed_len();
                 let account_size = TokenAccount::get_packed_len();
                 let mut account_data = vec![0; account_size];
                 let mut account_data = vec![0; account_size];
@@ -8625,10 +8664,10 @@ pub mod tests {
                     mint,
                     mint,
                     owner,
                     owner,
                     delegate: COption::Some(delegate),
                     delegate: COption::Some(delegate),
-                    amount: 420,
+                    amount,
                     state: TokenAccountState::Initialized,
                     state: TokenAccountState::Initialized,
-                    is_native: COption::Some(10),
-                    delegated_amount: 30,
+                    is_native: COption::Some(rent_exempt_amount),
+                    delegated_amount,
                     close_authority: COption::Some(owner),
                     close_authority: COption::Some(owner),
                 };
                 };
                 TokenAccount::pack(token_account, &mut account_data).unwrap();
                 TokenAccount::pack(token_account, &mut account_data).unwrap();
@@ -8645,8 +8684,8 @@ pub mod tests {
                 let mut mint_data = vec![0; mint_size];
                 let mut mint_data = vec![0; mint_size];
                 let mint_state = Mint {
                 let mint_state = Mint {
                     mint_authority: COption::Some(owner),
                     mint_authority: COption::Some(owner),
-                    supply: 500,
-                    decimals: 2,
+                    supply,
+                    decimals,
                     is_initialized: true,
                     is_initialized: true,
                     freeze_authority: COption::Some(owner),
                     freeze_authority: COption::Some(owner),
                 };
                 };
@@ -8658,7 +8697,11 @@ pub mod tests {
                     ..Account::default()
                     ..Account::default()
                 });
                 });
                 bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
                 bank.store_account(&Pubkey::from_str(&mint.to_string()).unwrap(), &mint_account);
-                ("spl-token", account_size, mint_size)
+                let additional_data = SplTokenAdditionalData {
+                    decimals,
+                    interest_bearing_config: None,
+                };
+                ("spl-token", account_size, mint_size, additional_data)
             };
             };
 
 
             let req = format!(
             let req = format!(
@@ -8667,6 +8710,11 @@ pub mod tests {
             let res = io.handle_request_sync(&req, meta.clone());
             let res = io.handle_request_sync(&req, meta.clone());
             let result: Value = serde_json::from_str(&res.expect("actual response"))
             let result: Value = serde_json::from_str(&res.expect("actual response"))
                 .expect("actual response deserialization");
                 .expect("actual response deserialization");
+            let token_ui_amount = token_amount_to_ui_amount_v2(amount, &additional_data);
+            let delegated_ui_amount =
+                token_amount_to_ui_amount_v2(delegated_amount, &additional_data);
+            let rent_exempt_ui_amount =
+                token_amount_to_ui_amount_v2(rent_exempt_amount, &additional_data);
             let mut expected_value = json!({
             let mut expected_value = json!({
                 "program": program_name,
                 "program": program_name,
                 "space": account_size,
                 "space": account_size,
@@ -8675,27 +8723,12 @@ pub mod tests {
                     "info": {
                     "info": {
                         "mint": mint.to_string(),
                         "mint": mint.to_string(),
                         "owner": owner.to_string(),
                         "owner": owner.to_string(),
-                        "tokenAmount": {
-                            "uiAmount": 4.2,
-                            "decimals": 2,
-                            "amount": "420",
-                            "uiAmountString": "4.2",
-                        },
+                        "tokenAmount": json!(token_ui_amount),
                         "delegate": delegate.to_string(),
                         "delegate": delegate.to_string(),
                         "state": "initialized",
                         "state": "initialized",
                         "isNative": true,
                         "isNative": true,
-                        "rentExemptReserve": {
-                            "uiAmount": 0.1,
-                            "decimals": 2,
-                            "amount": "10",
-                            "uiAmountString": "0.1",
-                        },
-                        "delegatedAmount": {
-                            "uiAmount": 0.3,
-                            "decimals": 2,
-                            "amount": "30",
-                            "uiAmountString": "0.3",
-                        },
+                        "rentExemptReserve": json!(rent_exempt_ui_amount),
+                        "delegatedAmount": json!(delegated_ui_amount),
                         "closeAuthority": owner.to_string(),
                         "closeAuthority": owner.to_string(),
                     }
                     }
                 }
                 }
@@ -8743,6 +8776,16 @@ pub mod tests {
                         "state": {
                         "state": {
                             "closeAuthority": owner.to_string(),
                             "closeAuthority": owner.to_string(),
                         }
                         }
+                    },
+                    {
+                        "extension": "interestBearingConfig",
+                        "state": {
+                            "currentRate": 500,
+                            "initializationTimestamp": bank.clock().unix_timestamp.saturating_sub(1_000_000),
+                            "lastUpdateTimestamp": bank.clock().unix_timestamp,
+                            "preUpdateAverageRate": 500,
+                            "rateAuthority": null,
+                        }
                     }
                     }
                 ]);
                 ]);
             }
             }

+ 2 - 2
rpc/src/rpc_pubsub.rs

@@ -610,7 +610,7 @@ mod tests {
         base64::{prelude::BASE64_STANDARD, Engine},
         base64::{prelude::BASE64_STANDARD, Engine},
         jsonrpc_core::{IoHandler, Response},
         jsonrpc_core::{IoHandler, Response},
         serial_test::serial,
         serial_test::serial,
-        solana_account_decoder::{parse_account_data::parse_account_data, UiAccountEncoding},
+        solana_account_decoder::{parse_account_data::parse_account_data_v2, UiAccountEncoding},
         solana_rpc_client_api::response::{
         solana_rpc_client_api::response::{
             ProcessedSignatureResult, ReceivedSignatureResult, RpcSignatureResult, SlotInfo,
             ProcessedSignatureResult, ReceivedSignatureResult, RpcSignatureResult, SlotInfo,
         },
         },
@@ -1049,7 +1049,7 @@ mod tests {
             .get_account(&nonce_account.pubkey())
             .get_account(&nonce_account.pubkey())
             .unwrap();
             .unwrap();
         let expected_data = account.data();
         let expected_data = account.data();
-        let expected_data = parse_account_data(
+        let expected_data = parse_account_data_v2(
             &nonce_account.pubkey(),
             &nonce_account.pubkey(),
             &system_program::id(),
             &system_program::id(),
             expected_data,
             expected_data,

+ 11 - 3
rpc/src/transaction_status_service.rs

@@ -198,7 +198,9 @@ pub(crate) mod tests {
         crate::transaction_notifier_interface::TransactionNotifier,
         crate::transaction_notifier_interface::TransactionNotifier,
         crossbeam_channel::unbounded,
         crossbeam_channel::unbounded,
         dashmap::DashMap,
         dashmap::DashMap,
-        solana_account_decoder::parse_token::token_amount_to_ui_amount,
+        solana_account_decoder::{
+            parse_account_data::SplTokenAdditionalData, parse_token::token_amount_to_ui_amount_v2,
+        },
         solana_ledger::{genesis_utils::create_genesis_config, get_tmp_ledger_path_auto_delete},
         solana_ledger::{genesis_utils::create_genesis_config, get_tmp_ledger_path_auto_delete},
         solana_runtime::bank::{Bank, TransactionBalancesSet},
         solana_runtime::bank::{Bank, TransactionBalancesSet},
         solana_sdk::{
         solana_sdk::{
@@ -344,7 +346,10 @@ pub(crate) mod tests {
         let pre_token_balance = TransactionTokenBalance {
         let pre_token_balance = TransactionTokenBalance {
             account_index: 0,
             account_index: 0,
             mint: Pubkey::new_unique().to_string(),
             mint: Pubkey::new_unique().to_string(),
-            ui_token_amount: token_amount_to_ui_amount(42, 2),
+            ui_token_amount: token_amount_to_ui_amount_v2(
+                42,
+                &SplTokenAdditionalData::with_decimals(2),
+            ),
             owner: owner.clone(),
             owner: owner.clone(),
             program_id: token_program_id.clone(),
             program_id: token_program_id.clone(),
         };
         };
@@ -352,7 +357,10 @@ pub(crate) mod tests {
         let post_token_balance = TransactionTokenBalance {
         let post_token_balance = TransactionTokenBalance {
             account_index: 0,
             account_index: 0,
             mint: Pubkey::new_unique().to_string(),
             mint: Pubkey::new_unique().to_string(),
-            ui_token_amount: token_amount_to_ui_amount(58, 2),
+            ui_token_amount: token_amount_to_ui_amount_v2(
+                58,
+                &SplTokenAdditionalData::with_decimals(2),
+            ),
             owner,
             owner,
             program_id: token_program_id,
             program_id: token_program_id,
         };
         };

+ 12 - 5
transaction-status/src/parse_token.rs

@@ -10,7 +10,10 @@ use {
         transfer_hook::*,
         transfer_hook::*,
     },
     },
     serde_json::{json, Map, Value},
     serde_json::{json, Map, Value},
-    solana_account_decoder::parse_token::{token_amount_to_ui_amount, UiAccountState},
+    solana_account_decoder::{
+        parse_account_data::SplTokenAdditionalData,
+        parse_token::{token_amount_to_ui_amount_v2, UiAccountState},
+    },
     solana_sdk::{
     solana_sdk::{
         instruction::{AccountMeta, CompiledInstruction, Instruction},
         instruction::{AccountMeta, CompiledInstruction, Instruction},
         message::AccountKeys,
         message::AccountKeys,
@@ -363,11 +366,12 @@ pub fn parse_token(
             }
             }
             TokenInstruction::TransferChecked { amount, decimals } => {
             TokenInstruction::TransferChecked { amount, decimals } => {
                 check_num_token_accounts(&instruction.accounts, 4)?;
                 check_num_token_accounts(&instruction.accounts, 4)?;
+                let additional_data = SplTokenAdditionalData::with_decimals(decimals);
                 let mut value = json!({
                 let mut value = json!({
                     "source": account_keys[instruction.accounts[0] as usize].to_string(),
                     "source": account_keys[instruction.accounts[0] as usize].to_string(),
                     "mint": account_keys[instruction.accounts[1] as usize].to_string(),
                     "mint": account_keys[instruction.accounts[1] as usize].to_string(),
                     "destination": account_keys[instruction.accounts[2] as usize].to_string(),
                     "destination": account_keys[instruction.accounts[2] as usize].to_string(),
-                    "tokenAmount": token_amount_to_ui_amount(amount, decimals),
+                    "tokenAmount": token_amount_to_ui_amount_v2(amount, &additional_data),
                 });
                 });
                 let map = value.as_object_mut().unwrap();
                 let map = value.as_object_mut().unwrap();
                 parse_signers(
                 parse_signers(
@@ -385,11 +389,12 @@ pub fn parse_token(
             }
             }
             TokenInstruction::ApproveChecked { amount, decimals } => {
             TokenInstruction::ApproveChecked { amount, decimals } => {
                 check_num_token_accounts(&instruction.accounts, 4)?;
                 check_num_token_accounts(&instruction.accounts, 4)?;
+                let additional_data = SplTokenAdditionalData::with_decimals(decimals);
                 let mut value = json!({
                 let mut value = json!({
                     "source": account_keys[instruction.accounts[0] as usize].to_string(),
                     "source": account_keys[instruction.accounts[0] as usize].to_string(),
                     "mint": account_keys[instruction.accounts[1] as usize].to_string(),
                     "mint": account_keys[instruction.accounts[1] as usize].to_string(),
                     "delegate": account_keys[instruction.accounts[2] as usize].to_string(),
                     "delegate": account_keys[instruction.accounts[2] as usize].to_string(),
-                    "tokenAmount": token_amount_to_ui_amount(amount, decimals),
+                    "tokenAmount": token_amount_to_ui_amount_v2(amount, &additional_data),
                 });
                 });
                 let map = value.as_object_mut().unwrap();
                 let map = value.as_object_mut().unwrap();
                 parse_signers(
                 parse_signers(
@@ -407,10 +412,11 @@ pub fn parse_token(
             }
             }
             TokenInstruction::MintToChecked { amount, decimals } => {
             TokenInstruction::MintToChecked { amount, decimals } => {
                 check_num_token_accounts(&instruction.accounts, 3)?;
                 check_num_token_accounts(&instruction.accounts, 3)?;
+                let additional_data = SplTokenAdditionalData::with_decimals(decimals);
                 let mut value = json!({
                 let mut value = json!({
                     "mint": account_keys[instruction.accounts[0] as usize].to_string(),
                     "mint": account_keys[instruction.accounts[0] as usize].to_string(),
                     "account": account_keys[instruction.accounts[1] as usize].to_string(),
                     "account": account_keys[instruction.accounts[1] as usize].to_string(),
-                    "tokenAmount": token_amount_to_ui_amount(amount, decimals),
+                    "tokenAmount": token_amount_to_ui_amount_v2(amount, &additional_data),
                 });
                 });
                 let map = value.as_object_mut().unwrap();
                 let map = value.as_object_mut().unwrap();
                 parse_signers(
                 parse_signers(
@@ -428,10 +434,11 @@ pub fn parse_token(
             }
             }
             TokenInstruction::BurnChecked { amount, decimals } => {
             TokenInstruction::BurnChecked { amount, decimals } => {
                 check_num_token_accounts(&instruction.accounts, 3)?;
                 check_num_token_accounts(&instruction.accounts, 3)?;
+                let additional_data = SplTokenAdditionalData::with_decimals(decimals);
                 let mut value = json!({
                 let mut value = json!({
                     "account": account_keys[instruction.accounts[0] as usize].to_string(),
                     "account": account_keys[instruction.accounts[0] as usize].to_string(),
                     "mint": account_keys[instruction.accounts[1] as usize].to_string(),
                     "mint": account_keys[instruction.accounts[1] as usize].to_string(),
-                    "tokenAmount": token_amount_to_ui_amount(amount, decimals),
+                    "tokenAmount": token_amount_to_ui_amount_v2(amount, &additional_data),
                 });
                 });
                 let map = value.as_object_mut().unwrap();
                 let map = value.as_object_mut().unwrap();
                 parse_signers(
                 parse_signers(

+ 3 - 2
transaction-status/src/parse_token/extension/transfer_fee.rs

@@ -44,12 +44,13 @@ pub(in crate::parse_token) fn parse_transfer_fee_instruction(
             fee,
             fee,
         } => {
         } => {
             check_num_token_accounts(account_indexes, 4)?;
             check_num_token_accounts(account_indexes, 4)?;
+            let additional_data = SplTokenAdditionalData::with_decimals(decimals);
             let mut value = json!({
             let mut value = json!({
                 "source": account_keys[account_indexes[0] as usize].to_string(),
                 "source": account_keys[account_indexes[0] as usize].to_string(),
                 "mint": account_keys[account_indexes[1] as usize].to_string(),
                 "mint": account_keys[account_indexes[1] as usize].to_string(),
                 "destination": account_keys[account_indexes[2] as usize].to_string(),
                 "destination": account_keys[account_indexes[2] as usize].to_string(),
-                "tokenAmount": token_amount_to_ui_amount(amount, decimals),
-                "feeAmount": token_amount_to_ui_amount(fee, decimals),
+                "tokenAmount": token_amount_to_ui_amount_v2(amount, &additional_data),
+                "feeAmount": token_amount_to_ui_amount_v2(fee, &additional_data),
             });
             });
             let map = value.as_object_mut().unwrap();
             let map = value.as_object_mut().unwrap();
             parse_signers(
             parse_signers(