* Deprecate UiTokenAmount::ui_amount (#15616)
* Add TokenAmount::ui_amount_string
* Fixup solana-tokens
* Update docs
(cherry picked from commit 19ac79b5cc)
# Conflicts:
#	storage-proto/proto/solana.storage.confirmed_block.rs
* Fix conflicts
Co-authored-by: Tyera Eulberg <teulberg@gmail.com>
Co-authored-by: Tyera Eulberg <tyera@solana.com>
			
			
This commit is contained in:
		| @@ -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)] | ||||
|   | ||||
| @@ -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<AccountState> 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<f64>, | ||||
|     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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -5695,9 +5695,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!( | ||||
| @@ -5720,9 +5721,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!( | ||||
| @@ -6018,17 +6020,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(), | ||||
|                     } | ||||
|                 } | ||||
|             ] | ||||
| @@ -6103,6 +6107,7 @@ pub mod tests { | ||||
|                             "uiAmount": 4.2, | ||||
|                             "decimals": 2, | ||||
|                             "amount": "420", | ||||
|                             "uiAmountString": "4.2", | ||||
|                         }, | ||||
|                         "delegate": delegate.to_string(), | ||||
|                         "state": "initialized", | ||||
| @@ -6111,11 +6116,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(), | ||||
|                     } | ||||
|   | ||||
| @@ -677,7 +677,8 @@ The JSON structure of token balances is defined as a list of objects in the foll | ||||
| - `uiTokenAmount: <object>` - | ||||
|   - `amount: <string>` - Raw amount of tokens as a string, ignoring decimals. | ||||
|   - `decimals: <number>` - Number of decimals configured for token's mint. | ||||
|   - `uiAmount: <string>` - Token amount as a float, accounting for decimals. | ||||
|   - `uiAmount: <number | null>` - Token amount as a float, accounting for decimals. **DEPRECATED** | ||||
|   - `uiAmountString: <string>` - 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: <string>` - the balance, using mint-prescribed decimals | ||||
| - `amount: <string>` - the raw balance without decimals, a string representation of u64 | ||||
| - `decimals: <u8>` - number of base 10 digits to the right of the decimal place | ||||
| - `uiAmount: <number | null>` - the balance, using mint-prescribed decimals **DEPRECATED** | ||||
| - `uiAmountString: <string>` - 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: <string>` - the address of the token account | ||||
| - `uiAmount: <string>` - the token account balance, using mint-prescribed decimals | ||||
| - `amount: <string>` - the raw token account balance without decimals, a string representation of u64 | ||||
| - `decimals: <u8>` - number of base 10 digits to the right of the decimal place | ||||
| - `uiAmount: <number | null>` - the token account balance, using mint-prescribed decimals **DEPRECATED** | ||||
| - `uiAmountString: <string>` - 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: <string>` - the total token supply, using mint-prescribed decimals | ||||
| - `amount: <string>` - the raw total token supply without decimals, a string representation of u64 | ||||
| - `decimals: <u8>` - number of base 10 digits to the right of the decimal place | ||||
| - `uiAmount: <number | null>` - the total token supply, using mint-prescribed decimals **DEPRECATED** | ||||
| - `uiAmountString: <string>` - 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 | ||||
|   | ||||
| @@ -104,6 +104,8 @@ pub struct UiTokenAmount { | ||||
|     pub decimals: u32, | ||||
|     #[prost(string, tag = "3")] | ||||
|     pub amount: std::string::String, | ||||
|     #[prost(string, tag = "4")] | ||||
|     pub ui_amount_string: std::string::String, | ||||
| } | ||||
| #[derive(Clone, PartialEq, ::prost::Message)] | ||||
| pub struct Reward { | ||||
|   | ||||
| @@ -70,6 +70,7 @@ message UiTokenAmount { | ||||
|     double ui_amount = 1; | ||||
|     uint32 decimals = 2; | ||||
|     string amount = 3; | ||||
|     string ui_amount_string = 4; | ||||
| } | ||||
|  | ||||
| enum RewardType { | ||||
|   | ||||
| @@ -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<TransactionTokenBalance> 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<generated::TokenBalance> 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, | ||||
|                     ) | ||||
|                 }, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -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::<DateTime<Utc>>().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"); | ||||
|         } | ||||
|   | ||||
| @@ -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(()) | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -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", | ||||
|                    } | ||||
|                 }) | ||||
|             } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user