diff --git a/account-decoder/src/lib.rs b/account-decoder/src/lib.rs index d3222128a9..611be374e4 100644 --- a/account-decoder/src/lib.rs +++ b/account-decoder/src/lib.rs @@ -24,6 +24,7 @@ use { }; pub type StringAmount = String; +pub type StringDecimals = String; /// A duplicate representation of an Account for pretty JSON serialization #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/account-decoder/src/parse_token.rs b/account-decoder/src/parse_token.rs index 2b416a519b..2799a1297e 100644 --- a/account-decoder/src/parse_token.rs +++ b/account-decoder/src/parse_token.rs @@ -1,6 +1,6 @@ use crate::{ parse_account_data::{ParsableAccount, ParseAccountError}, - StringAmount, + StringAmount, StringDecimals, }; use solana_sdk::pubkey::Pubkey; use spl_token_v2_0::{ @@ -158,46 +158,64 @@ impl From for UiAccountState { } } +pub fn real_number_string(amount: u64, decimals: u8) -> StringDecimals { + let decimals = decimals as usize; + if decimals > 0 { + // Left-pad zeros to decimals + 1, so we at least have an integer zero + let mut s = format!("{:01$}", amount, decimals + 1); + // Add the decimal point (Sorry, "," locales!) + s.insert(s.len() - decimals, '.'); + s + } else { + amount.to_string() + } +} + +pub fn real_number_string_trimmed(amount: u64, decimals: u8) -> StringDecimals { + let s = real_number_string(amount, decimals); + let zeros_trimmed = s.trim_end_matches('0'); + let decimal_trimmed = zeros_trimmed.trim_end_matches('.'); + decimal_trimmed.to_string() +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "camelCase")] pub struct UiTokenAmount { - pub ui_amount: f64, + pub ui_amount: Option, pub decimals: u8, pub amount: StringAmount, + pub ui_amount_string: StringDecimals, } impl UiTokenAmount { pub fn real_number_string(&self) -> String { - let decimals = self.decimals as usize; - if decimals > 0 { - let amount = u64::from_str(&self.amount).unwrap_or(0); - - // Left-pad zeros to decimals + 1, so we at least have an integer zero - let mut s = format!("{:01$}", amount, decimals + 1); - - // Add the decimal point (Sorry, "," locales!) - s.insert(s.len() - decimals, '.'); - s - } else { - self.amount.clone() - } + real_number_string( + u64::from_str(&self.amount).unwrap_or_default(), + self.decimals as u8, + ) } pub fn real_number_string_trimmed(&self) -> String { - let s = self.real_number_string(); - let zeros_trimmed = s.trim_end_matches('0'); - let decimal_trimmed = zeros_trimmed.trim_end_matches('.'); - decimal_trimmed.to_string() + if !self.ui_amount_string.is_empty() { + self.ui_amount_string.clone() + } else { + real_number_string_trimmed( + u64::from_str(&self.amount).unwrap_or_default(), + self.decimals as u8, + ) + } } } pub fn token_amount_to_ui_amount(amount: u64, decimals: u8) -> UiTokenAmount { - // Use `amount_to_ui_amount()` once spl_token is bumped to a version that supports it: https://github.com/solana-labs/solana-program-library/pull/211 - let amount_decimals = amount as f64 / 10_usize.pow(decimals as u32) as f64; + let amount_decimals = 10_usize + .checked_pow(decimals as u32) + .map(|dividend| amount as f64 / dividend as f64); UiTokenAmount { ui_amount: amount_decimals, decimals, amount: amount.to_string(), + ui_amount_string: real_number_string_trimmed(amount, decimals), } } @@ -253,9 +271,10 @@ mod test { mint: mint_pubkey.to_string(), owner: owner_pubkey.to_string(), token_amount: UiTokenAmount { - ui_amount: 0.42, + ui_amount: Some(0.42), decimals: 2, - amount: "42".to_string() + amount: "42".to_string(), + ui_amount_string: "0.42".to_string() }, delegate: None, state: UiAccountState::Initialized, @@ -336,17 +355,51 @@ mod test { #[test] fn test_ui_token_amount_real_string() { + assert_eq!(&real_number_string(1, 0), "1"); + assert_eq!(&real_number_string_trimmed(1, 0), "1"); let token_amount = token_amount_to_ui_amount(1, 0); - assert_eq!(&token_amount.real_number_string(), "1"); - assert_eq!(&token_amount.real_number_string_trimmed(), "1"); + assert_eq!( + token_amount.ui_amount_string, + real_number_string_trimmed(1, 0) + ); + assert_eq!(token_amount.ui_amount, Some(1.0)); + assert_eq!(&real_number_string(1, 9), "0.000000001"); + assert_eq!(&real_number_string_trimmed(1, 9), "0.000000001"); let token_amount = token_amount_to_ui_amount(1, 9); - assert_eq!(&token_amount.real_number_string(), "0.000000001"); - assert_eq!(&token_amount.real_number_string_trimmed(), "0.000000001"); + assert_eq!( + token_amount.ui_amount_string, + real_number_string_trimmed(1, 9) + ); + 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_trimmed(1_000_000_000, 9), "1"); let token_amount = token_amount_to_ui_amount(1_000_000_000, 9); - assert_eq!(&token_amount.real_number_string(), "1.000000000"); - assert_eq!(&token_amount.real_number_string_trimmed(), "1"); + assert_eq!( + token_amount.ui_amount_string, + real_number_string_trimmed(1_000_000_000, 9) + ); + 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_trimmed(1_234_567_890, 3), "1234567.89"); let token_amount = token_amount_to_ui_amount(1_234_567_890, 3); - assert_eq!(&token_amount.real_number_string(), "1234567.890"); - assert_eq!(&token_amount.real_number_string_trimmed(), "1234567.89"); + assert_eq!( + token_amount.ui_amount_string, + real_number_string_trimmed(1_234_567_890, 3) + ); + assert_eq!(token_amount.ui_amount, Some(1234567.89)); + assert_eq!( + &real_number_string(1_234_567_890, 25), + "0.0000000000000001234567890" + ); + assert_eq!( + &real_number_string_trimmed(1_234_567_890, 25), + "0.000000000000000123456789" + ); + let token_amount = token_amount_to_ui_amount(1_234_567_890, 20); + assert_eq!( + token_amount.ui_amount_string, + real_number_string_trimmed(1_234_567_890, 20) + ); + assert_eq!(token_amount.ui_amount, None); } } diff --git a/core/src/rpc.rs b/core/src/rpc.rs index 289b7b3be7..dff6397ab2 100644 --- a/core/src/rpc.rs +++ b/core/src/rpc.rs @@ -5699,9 +5699,10 @@ pub mod tests { let balance: UiTokenAmount = serde_json::from_value(result["result"]["value"].clone()).unwrap(); let error = f64::EPSILON; - assert!((balance.ui_amount - 4.2).abs() < error); + assert!((balance.ui_amount.unwrap() - 4.2).abs() < error); assert_eq!(balance.amount, 420.to_string()); assert_eq!(balance.decimals, 2); + assert_eq!(balance.ui_amount_string, "4.2".to_string()); // Test non-existent token account let req = format!( @@ -5724,9 +5725,10 @@ pub mod tests { let supply: UiTokenAmount = serde_json::from_value(result["result"]["value"].clone()).unwrap(); let error = f64::EPSILON; - assert!((supply.ui_amount - 5.0).abs() < error); + assert!((supply.ui_amount.unwrap() - 5.0).abs() < error); assert_eq!(supply.amount, 500.to_string()); assert_eq!(supply.decimals, 2); + assert_eq!(supply.ui_amount_string, "5".to_string()); // Test non-existent mint address let req = format!( @@ -6022,17 +6024,19 @@ pub mod tests { RpcTokenAccountBalance { address: token_with_different_mint_pubkey.to_string(), amount: UiTokenAmount { - ui_amount: 0.42, + ui_amount: Some(0.42), decimals: 2, amount: "42".to_string(), + ui_amount_string: "0.42".to_string(), } }, RpcTokenAccountBalance { address: token_with_smaller_balance.to_string(), amount: UiTokenAmount { - ui_amount: 0.1, + ui_amount: Some(0.1), decimals: 2, amount: "10".to_string(), + ui_amount_string: "0.1".to_string(), } } ] @@ -6107,6 +6111,7 @@ pub mod tests { "uiAmount": 4.2, "decimals": 2, "amount": "420", + "uiAmountString": "4.2", }, "delegate": delegate.to_string(), "state": "initialized", @@ -6115,11 +6120,13 @@ pub mod tests { "uiAmount": 0.1, "decimals": 2, "amount": "10", + "uiAmountString": "0.1", }, "delegatedAmount": { "uiAmount": 0.3, "decimals": 2, "amount": "30", + "uiAmountString": "0.3", }, "closeAuthority": owner.to_string(), } diff --git a/docs/src/developing/clients/jsonrpc-api.md b/docs/src/developing/clients/jsonrpc-api.md index 7a0b4ed826..a53aca9b50 100644 --- a/docs/src/developing/clients/jsonrpc-api.md +++ b/docs/src/developing/clients/jsonrpc-api.md @@ -677,7 +677,8 @@ The JSON structure of token balances is defined as a list of objects in the foll - `uiTokenAmount: ` - - `amount: ` - Raw amount of tokens as a string, ignoring decimals. - `decimals: ` - Number of decimals configured for token's mint. - - `uiAmount: ` - Token amount as a float, accounting for decimals. + - `uiAmount: ` - Token amount as a float, accounting for decimals. **DEPRECATED** + - `uiAmountString: ` - Token amount as a string, accounting for decimals. ### getConfirmedBlocks @@ -2380,9 +2381,10 @@ Returns the token balance of an SPL Token account. **UNSTABLE** The result will be an RpcResponse JSON object with `value` equal to a JSON object containing: -- `uiAmount: ` - the balance, using mint-prescribed decimals - `amount: ` - the raw balance without decimals, a string representation of u64 - `decimals: ` - number of base 10 digits to the right of the decimal place +- `uiAmount: ` - the balance, using mint-prescribed decimals **DEPRECATED** +- `uiAmountString: ` - the balance as a string, using mint-prescribed decimals #### Example: @@ -2402,9 +2404,10 @@ Result: "slot": 1114 }, "value": { - "uiAmount": "98.64", "amount": "9864", - "decimals": 2 + "decimals": 2, + "uiAmount": 98.64, + "uiAmountString": "98.64", }, "id": 1 } @@ -2477,8 +2480,9 @@ Result: "info": { "tokenAmount": { "amount": "1", - "uiAmount": "0.1", - "decimals": 1 + "decimals": 1, + "uiAmount": 0.1, + "uiAmountString": "0.1", }, "delegate": "4Nd1mBQtrMJVYVfKf2PJy9NZUZdTAsp7D4xWLs4gDB4T", "delegatedAmount": 1, @@ -2566,8 +2570,9 @@ Result: "info": { "tokenAmount": { "amount": "1", - "uiAmount": "0.1", - "decimals": 1 + "decimals": 1, + "uiAmount": 0.1, + "uiAmountString": "0.1", }, "delegate": null, "delegatedAmount": 1, @@ -2603,9 +2608,10 @@ Returns the 20 largest accounts of a particular SPL Token type. **UNSTABLE** The result will be an RpcResponse JSON object with `value` equal to an array of JSON objects containing: - `address: ` - the address of the token account -- `uiAmount: ` - the token account balance, using mint-prescribed decimals - `amount: ` - the raw token account balance without decimals, a string representation of u64 - `decimals: ` - number of base 10 digits to the right of the decimal place +- `uiAmount: ` - the token account balance, using mint-prescribed decimals **DEPRECATED** +- `uiAmountString: ` - the token account balance as a string, using mint-prescribed decimals #### Example: @@ -2628,13 +2634,15 @@ Result: "address": "FYjHNoFtSQ5uijKrZFyYAxvEr87hsKXkXcxkcmkBAf4r", "amount": "771", "decimals": 2, - "uiAmount": "7.71" + "uiAmount": 7.71, + "uiAmountString": "7.71" }, { "address": "BnsywxTcaYeNUtzrPxQUvzAWxfzZe3ZLUJ4wMMuLESnu", "amount": "229", "decimals": 2, - "uiAmount": "2.29" + "uiAmount": 2.29, + "uiAmountString": "2.29" } ] }, @@ -2655,9 +2663,10 @@ Returns the total supply of an SPL Token type. **UNSTABLE** The result will be an RpcResponse JSON object with `value` equal to a JSON object containing: -- `uiAmount: ` - the total token supply, using mint-prescribed decimals - `amount: ` - the raw total token supply without decimals, a string representation of u64 - `decimals: ` - number of base 10 digits to the right of the decimal place +- `uiAmount: ` - the total token supply, using mint-prescribed decimals **DEPRECATED** +- `uiAmountString: ` - the total token supply as a string, using mint-prescribed decimals #### Example: @@ -2676,9 +2685,10 @@ Result: "slot": 1114 }, "value": { - "uiAmount": "1000", "amount": "100000", - "decimals": 2 + "decimals": 2, + "uiAmount": 1000, + "uiAmountString": "1000", } }, "id": 1 diff --git a/storage-proto/proto/solana.storage.confirmed_block.rs b/storage-proto/proto/solana.storage.confirmed_block.rs index 929237c52d..3ca842dad7 100644 --- a/storage-proto/proto/solana.storage.confirmed_block.rs +++ b/storage-proto/proto/solana.storage.confirmed_block.rs @@ -104,6 +104,8 @@ pub struct UiTokenAmount { pub decimals: u32, #[prost(string, tag = "3")] pub amount: ::prost::alloc::string::String, + #[prost(string, tag = "4")] + pub ui_amount_string: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Reward { diff --git a/storage-proto/src/confirmed_block.proto b/storage-proto/src/confirmed_block.proto index 5875c60782..d492a359d6 100644 --- a/storage-proto/src/confirmed_block.proto +++ b/storage-proto/src/confirmed_block.proto @@ -70,6 +70,7 @@ message UiTokenAmount { double ui_amount = 1; uint32 decimals = 2; string amount = 3; + string ui_amount_string = 4; } enum RewardType { diff --git a/storage-proto/src/convert.rs b/storage-proto/src/convert.rs index 6117fb976c..9541ab0c0b 100644 --- a/storage-proto/src/convert.rs +++ b/storage-proto/src/convert.rs @@ -1,5 +1,5 @@ use crate::StoredExtendedRewards; -use solana_account_decoder::parse_token::UiTokenAmount; +use solana_account_decoder::parse_token::{real_number_string_trimmed, UiTokenAmount}; use solana_sdk::{ hash::Hash, instruction::CompiledInstruction, @@ -14,7 +14,10 @@ use solana_transaction_status::{ ConfirmedBlock, InnerInstructions, Reward, RewardType, TransactionByAddrInfo, TransactionStatusMeta, TransactionTokenBalance, TransactionWithStatusMeta, }; -use std::convert::{TryFrom, TryInto}; +use std::{ + convert::{TryFrom, TryInto}, + str::FromStr, +}; pub mod generated { include!(concat!( @@ -383,9 +386,10 @@ impl From for generated::TokenBalance { account_index: value.account_index as u32, mint: value.mint, ui_token_amount: Some(generated::UiTokenAmount { - ui_amount: value.ui_token_amount.ui_amount, + ui_amount: value.ui_token_amount.ui_amount.unwrap_or_default(), decimals: value.ui_token_amount.decimals as u32, amount: value.ui_token_amount.amount, + ui_amount_string: value.ui_token_amount.ui_amount_string, }), } } @@ -398,9 +402,21 @@ impl From for TransactionTokenBalance { account_index: value.account_index as u8, mint: value.mint, ui_token_amount: UiTokenAmount { - ui_amount: ui_token_amount.ui_amount, + ui_amount: if (ui_token_amount.ui_amount - f64::default()).abs() > f64::EPSILON { + Some(ui_token_amount.ui_amount) + } else { + None + }, decimals: ui_token_amount.decimals as u8, - amount: ui_token_amount.amount, + amount: ui_token_amount.amount.clone(), + ui_amount_string: if !ui_token_amount.ui_amount_string.is_empty() { + ui_token_amount.ui_amount_string + } else { + real_number_string_trimmed( + u64::from_str(&ui_token_amount.amount).unwrap_or_default(), + ui_token_amount.decimals as u8, + ) + }, }, } } diff --git a/tokens/src/commands.rs b/tokens/src/commands.rs index d2a6187a5a..2abc8a8ce8 100644 --- a/tokens/src/commands.rs +++ b/tokens/src/commands.rs @@ -12,7 +12,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use pickledb::PickleDb; use serde::{Deserialize, Serialize}; use solana_account_decoder::parse_token::{ - pubkey_from_spl_token_v2_0, spl_token_v2_0_pubkey, token_amount_to_ui_amount, + pubkey_from_spl_token_v2_0, real_number_string, spl_token_v2_0_pubkey, }; use solana_client::{ client_error::{ClientError, Result as ClientResult}, @@ -103,8 +103,8 @@ pub enum Error { ClientError(#[from] ClientError), #[error("Missing lockup authority")] MissingLockupAuthority, - #[error("insufficient funds in {0:?}, requires {1} SOL")] - InsufficientFunds(FundingSources, f64), + #[error("insufficient funds in {0:?}, requires {1}")] + InsufficientFunds(FundingSources, String), #[error("Program error")] ProgramError(#[from] ProgramError), #[error("Exit signal received")] @@ -273,33 +273,34 @@ fn build_messages( Some(allocation.lockup_date.parse::>().unwrap()) }; - let (display_amount, decimals, do_create_associated_token_account) = - if let Some(spl_token_args) = &args.spl_token_args { - let wallet_address = allocation.recipient.parse().unwrap(); - let associated_token_address = get_associated_token_address( - &wallet_address, - &spl_token_v2_0_pubkey(&spl_token_args.mint), - ); - let do_create_associated_token_account = - client.get_multiple_accounts(&[pubkey_from_spl_token_v2_0( - &associated_token_address, - )])?[0] - .is_none(); - if do_create_associated_token_account { - *created_accounts += 1; - } - ( - token_amount_to_ui_amount(allocation.amount, spl_token_args.decimals).ui_amount, - spl_token_args.decimals as usize, - do_create_associated_token_account, - ) - } else { - (lamports_to_sol(allocation.amount), 9, false) - }; - println!( - "{:<44} {:>24.2$}", - allocation.recipient, display_amount, decimals - ); + let do_create_associated_token_account = if let Some(spl_token_args) = &args.spl_token_args + { + let wallet_address = allocation.recipient.parse().unwrap(); + let associated_token_address = get_associated_token_address( + &wallet_address, + &spl_token_v2_0_pubkey(&spl_token_args.mint), + ); + let do_create_associated_token_account = client + .get_multiple_accounts(&[pubkey_from_spl_token_v2_0(&associated_token_address)])? + [0] + .is_none(); + if do_create_associated_token_account { + *created_accounts += 1; + } + println!( + "{:<44} {:>24}", + allocation.recipient, + real_number_string(allocation.amount, spl_token_args.decimals) + ); + do_create_associated_token_account + } else { + println!( + "{:<44} {:>24.9}", + allocation.recipient, + lamports_to_sol(allocation.amount) + ); + false + }; let instructions = distribution_instructions( allocation, &new_stake_account_keypair.pubkey(), @@ -719,7 +720,7 @@ fn check_payer_balances( if staker_balance < undistributed_tokens { return Err(Error::InsufficientFunds( vec![FundingSource::StakeAccount].into(), - lamports_to_sol(undistributed_tokens), + lamports_to_sol(undistributed_tokens).to_string(), )); } if args.fee_payer.pubkey() == unlocked_sol_source { @@ -727,7 +728,7 @@ fn check_payer_balances( if balance < fees + total_unlocked_sol { return Err(Error::InsufficientFunds( vec![FundingSource::SystemAccount, FundingSource::FeePayer].into(), - lamports_to_sol(fees + total_unlocked_sol), + lamports_to_sol(fees + total_unlocked_sol).to_string(), )); } } else { @@ -735,14 +736,14 @@ fn check_payer_balances( if fee_payer_balance < fees { return Err(Error::InsufficientFunds( vec![FundingSource::FeePayer].into(), - lamports_to_sol(fees), + lamports_to_sol(fees).to_string(), )); } let unlocked_sol_balance = client.get_balance(&unlocked_sol_source)?; if unlocked_sol_balance < total_unlocked_sol { return Err(Error::InsufficientFunds( vec![FundingSource::SystemAccount].into(), - lamports_to_sol(total_unlocked_sol), + lamports_to_sol(total_unlocked_sol).to_string(), )); } } @@ -751,7 +752,7 @@ fn check_payer_balances( if balance < fees + undistributed_tokens { return Err(Error::InsufficientFunds( vec![FundingSource::SystemAccount, FundingSource::FeePayer].into(), - lamports_to_sol(fees + undistributed_tokens), + lamports_to_sol(fees + undistributed_tokens).to_string(), )); } } else { @@ -759,14 +760,14 @@ fn check_payer_balances( if fee_payer_balance < fees { return Err(Error::InsufficientFunds( vec![FundingSource::FeePayer].into(), - lamports_to_sol(fees), + lamports_to_sol(fees).to_string(), )); } let sender_balance = client.get_balance(&distribution_source)?; if sender_balance < undistributed_tokens { return Err(Error::InsufficientFunds( vec![FundingSource::SystemAccount].into(), - lamports_to_sol(undistributed_tokens), + lamports_to_sol(undistributed_tokens).to_string(), )); } } @@ -1415,7 +1416,7 @@ mod tests { sources, vec![FundingSource::SystemAccount, FundingSource::FeePayer].into() ); - assert!((amount - (allocation_amount + fees_in_sol)).abs() < f64::EPSILON); + assert_eq!(amount, (allocation_amount + fees_in_sol).to_string()); } else { panic!("check_payer_balances should have errored"); } @@ -1452,7 +1453,7 @@ mod tests { sources, vec![FundingSource::SystemAccount, FundingSource::FeePayer].into() ); - assert!((amount - (allocation_amount + fees_in_sol)).abs() < f64::EPSILON); + assert_eq!(amount, (allocation_amount + fees_in_sol).to_string()); } else { panic!("check_payer_balances should have errored"); } @@ -1508,7 +1509,7 @@ mod tests { let err_result = check_payer_balances(1, &allocations, &client, &args).unwrap_err(); if let Error::InsufficientFunds(sources, amount) = err_result { assert_eq!(sources, vec![FundingSource::SystemAccount].into()); - assert!((amount - allocation_amount).abs() < f64::EPSILON); + assert_eq!(amount, allocation_amount.to_string()); } else { panic!("check_payer_balances should have errored"); } @@ -1522,7 +1523,7 @@ mod tests { let err_result = check_payer_balances(1, &allocations, &client, &args).unwrap_err(); if let Error::InsufficientFunds(sources, amount) = err_result { assert_eq!(sources, vec![FundingSource::FeePayer].into()); - assert!((amount - fees_in_sol).abs() < f64::EPSILON); + assert_eq!(amount, fees_in_sol.to_string()); } else { panic!("check_payer_balances should have errored"); } @@ -1609,7 +1610,10 @@ mod tests { check_payer_balances(1, &expensive_allocations, &client, &args).unwrap_err(); if let Error::InsufficientFunds(sources, amount) = err_result { assert_eq!(sources, vec![FundingSource::StakeAccount].into()); - assert!((amount - (expensive_allocation_amount - unlocked_sol)).abs() < f64::EPSILON); + assert_eq!( + amount, + (expensive_allocation_amount - unlocked_sol).to_string() + ); } else { panic!("check_payer_balances should have errored"); } @@ -1631,7 +1635,7 @@ mod tests { sources, vec![FundingSource::SystemAccount, FundingSource::FeePayer].into() ); - assert!((amount - (unlocked_sol + fees_in_sol)).abs() < f64::EPSILON); + assert_eq!(amount, (unlocked_sol + fees_in_sol).to_string()); } else { panic!("check_payer_balances should have errored"); } @@ -1668,7 +1672,7 @@ mod tests { sources, vec![FundingSource::SystemAccount, FundingSource::FeePayer].into() ); - assert!((amount - (unlocked_sol + fees_in_sol)).abs() < f64::EPSILON); + assert_eq!(amount, (unlocked_sol + fees_in_sol).to_string()); } else { panic!("check_payer_balances should have errored"); } @@ -1731,7 +1735,7 @@ mod tests { let err_result = check_payer_balances(1, &allocations, &client, &args).unwrap_err(); if let Error::InsufficientFunds(sources, amount) = err_result { assert_eq!(sources, vec![FundingSource::SystemAccount].into()); - assert!((amount - unlocked_sol).abs() < f64::EPSILON); + assert_eq!(amount, unlocked_sol.to_string()); } else { panic!("check_payer_balances should have errored"); } @@ -1745,7 +1749,7 @@ mod tests { let err_result = check_payer_balances(1, &allocations, &client, &args).unwrap_err(); if let Error::InsufficientFunds(sources, amount) = err_result { assert_eq!(sources, vec![FundingSource::FeePayer].into()); - assert!((amount - fees_in_sol).abs() < f64::EPSILON); + assert_eq!(amount, fees_in_sol.to_string()); } else { panic!("check_payer_balances should have errored"); } diff --git a/tokens/src/spl_token.rs b/tokens/src/spl_token.rs index bc9aa2a45f..a5a2e1136a 100644 --- a/tokens/src/spl_token.rs +++ b/tokens/src/spl_token.rs @@ -4,7 +4,8 @@ use crate::{ }; use console::style; use solana_account_decoder::parse_token::{ - pubkey_from_spl_token_v2_0, spl_token_v2_0_pubkey, token_amount_to_ui_amount, + pubkey_from_spl_token_v2_0, real_number_string, real_number_string_trimmed, + spl_token_v2_0_pubkey, }; use solana_client::rpc_client::RpcClient; use solana_sdk::{instruction::Instruction, native_token::lamports_to_sol}; @@ -109,7 +110,7 @@ pub fn check_spl_token_balances( if fee_payer_balance < fees + account_creation_amount { return Err(Error::InsufficientFunds( vec![FundingSource::FeePayer].into(), - lamports_to_sol(fees + account_creation_amount), + lamports_to_sol(fees + account_creation_amount).to_string(), )); } let source_token_account = client @@ -119,7 +120,7 @@ pub fn check_spl_token_balances( if source_token.amount < allocation_amount { return Err(Error::InsufficientFunds( vec![FundingSource::SplTokenAccount].into(), - token_amount_to_ui_amount(allocation_amount, spl_token_args.decimals).ui_amount, + real_number_string_trimmed(allocation_amount, spl_token_args.decimals), )); } Ok(()) @@ -142,20 +143,12 @@ pub fn print_token_balances( let (actual, difference) = if let Ok(recipient_token) = SplTokenAccount::unpack(&recipient_account.data) { - let actual_ui_amount = - token_amount_to_ui_amount(recipient_token.amount, spl_token_args.decimals).ui_amount; - let expected_ui_amount = - token_amount_to_ui_amount(expected, spl_token_args.decimals).ui_amount; + let actual_ui_amount = real_number_string(recipient_token.amount, spl_token_args.decimals); + let delta_string = + real_number_string(recipient_token.amount - expected, spl_token_args.decimals); ( - style(format!( - "{:>24.1$}", - actual_ui_amount, spl_token_args.decimals as usize - )), - format!( - "{:>24.1$}", - actual_ui_amount - expected_ui_amount, - spl_token_args.decimals as usize - ), + style(format!("{:>24}", actual_ui_amount)), + format!("{:>24}", delta_string), ) } else { ( @@ -164,12 +157,11 @@ pub fn print_token_balances( ) }; println!( - "{:<44} {:>24.4$} {:>24} {:>24}", + "{:<44} {:>24} {:>24} {:>24}", allocation.recipient, - token_amount_to_ui_amount(expected, spl_token_args.decimals).ui_amount, + real_number_string(expected, spl_token_args.decimals), actual, difference, - spl_token_args.decimals as usize ); Ok(()) } diff --git a/tokens/src/token_display.rs b/tokens/src/token_display.rs index 236f3f0d4c..1a42787c11 100644 --- a/tokens/src/token_display.rs +++ b/tokens/src/token_display.rs @@ -1,4 +1,4 @@ -use solana_account_decoder::parse_token::token_amount_to_ui_amount; +use solana_account_decoder::parse_token::real_number_string_trimmed; use solana_sdk::native_token::lamports_to_sol; use std::{ fmt::{Debug, Display, Formatter, Result}, @@ -27,7 +27,7 @@ impl Token { write!(f, "{}{}", SOL_SYMBOL, amount) } TokenType::SplToken => { - let amount = token_amount_to_ui_amount(self.amount, self.decimals).ui_amount; + let amount = real_number_string_trimmed(self.amount, self.decimals); write!(f, "{} tokens", amount) } } diff --git a/transaction-status/src/parse_token.rs b/transaction-status/src/parse_token.rs index bf512b03ea..4bcb062b47 100644 --- a/transaction-status/src/parse_token.rs +++ b/transaction-status/src/parse_token.rs @@ -890,7 +890,8 @@ mod test { "tokenAmount": { "uiAmount": 0.42, "decimals": 2, - "amount": "42" + "amount": "42", + "uiAmountString": "0.42", } }) } @@ -922,7 +923,8 @@ mod test { "tokenAmount": { "uiAmount": 0.42, "decimals": 2, - "amount": "42" + "amount": "42", + "uiAmountString": "0.42", } }) } @@ -954,7 +956,8 @@ mod test { "tokenAmount": { "uiAmount": 0.42, "decimals": 2, - "amount": "42" + "amount": "42", + "uiAmountString": "0.42", } }) } @@ -986,7 +989,8 @@ mod test { "tokenAmount": { "uiAmount": 0.42, "decimals": 2, - "amount": "42" + "amount": "42", + "uiAmountString": "0.42", } }) } @@ -1016,7 +1020,8 @@ mod test { "tokenAmount": { "uiAmount": 0.42, "decimals": 2, - "amount": "42" + "amount": "42", + "uiAmountString": "0.42", } }) } @@ -1046,7 +1051,8 @@ mod test { "tokenAmount": { "uiAmount": 0.42, "decimals": 2, - "amount": "42" + "amount": "42", + "uiAmountString": "0.42", } }) }