2019-08-08 11:13:06 -06:00
|
|
|
use console::style;
|
2020-05-31 23:00:51 -07:00
|
|
|
use indicatif::{ProgressBar, ProgressStyle};
|
2020-04-15 20:51:05 -07:00
|
|
|
use solana_sdk::{
|
|
|
|
hash::Hash, native_token::lamports_to_sol, program_utils::limited_deserialize,
|
|
|
|
transaction::Transaction,
|
|
|
|
};
|
2020-07-01 14:06:40 -06:00
|
|
|
use solana_transaction_status::UiTransactionStatusMeta;
|
2020-06-17 23:09:33 -07:00
|
|
|
use std::{collections::HashMap, fmt, io};
|
2019-08-08 11:13:06 -06:00
|
|
|
|
2020-09-22 18:29:11 -06:00
|
|
|
pub fn build_balance_message(lamports: u64, use_lamports_unit: bool, show_unit: bool) -> String {
|
|
|
|
if use_lamports_unit {
|
|
|
|
let ess = if lamports == 1 { "" } else { "s" };
|
|
|
|
let unit = if show_unit {
|
|
|
|
format!(" lamport{}", ess)
|
|
|
|
} else {
|
|
|
|
"".to_string()
|
|
|
|
};
|
|
|
|
format!("{:?}{}", lamports, unit)
|
|
|
|
} else {
|
|
|
|
let sol = lamports_to_sol(lamports);
|
|
|
|
let sol_str = format!("{:.9}", sol);
|
|
|
|
let pretty_sol = sol_str.trim_end_matches('0').trim_end_matches('.');
|
|
|
|
let unit = if show_unit { " SOL" } else { "" };
|
|
|
|
format!("{}{}", pretty_sol, unit)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-08 11:13:06 -06:00
|
|
|
// Pretty print a "name value"
|
|
|
|
pub fn println_name_value(name: &str, value: &str) {
|
|
|
|
let styled_value = if value == "" {
|
|
|
|
style("(not set)").italic()
|
|
|
|
} else {
|
|
|
|
style(value)
|
|
|
|
};
|
|
|
|
println!("{} {}", style(name).bold(), styled_value);
|
|
|
|
}
|
2019-09-05 10:14:23 -07:00
|
|
|
|
2020-04-14 13:10:25 -06:00
|
|
|
pub fn writeln_name_value(f: &mut fmt::Formatter, name: &str, value: &str) -> fmt::Result {
|
|
|
|
let styled_value = if value == "" {
|
|
|
|
style("(not set)").italic()
|
|
|
|
} else {
|
|
|
|
style(value)
|
|
|
|
};
|
|
|
|
writeln!(f, "{} {}", style(name).bold(), styled_value)
|
|
|
|
}
|
|
|
|
|
2020-06-17 23:09:33 -07:00
|
|
|
pub fn format_labeled_address(pubkey: &str, address_labels: &HashMap<String, String>) -> String {
|
|
|
|
let label = address_labels.get(pubkey);
|
|
|
|
match label {
|
|
|
|
Some(label) => format!(
|
|
|
|
"{:.31} ({:.4}..{})",
|
|
|
|
label,
|
|
|
|
pubkey,
|
|
|
|
pubkey.split_at(pubkey.len() - 4).1
|
|
|
|
),
|
|
|
|
None => pubkey.to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 21:49:38 -06:00
|
|
|
pub fn println_signers(
|
|
|
|
blockhash: &Hash,
|
|
|
|
signers: &[String],
|
|
|
|
absent: &[String],
|
|
|
|
bad_sig: &[String],
|
|
|
|
) {
|
2019-11-25 21:09:57 -08:00
|
|
|
println!();
|
2020-03-18 21:49:38 -06:00
|
|
|
println!("Blockhash: {}", blockhash);
|
|
|
|
if !signers.is_empty() {
|
|
|
|
println!("Signers (Pubkey=Signature):");
|
|
|
|
signers.iter().for_each(|signer| println!(" {}", signer))
|
|
|
|
}
|
|
|
|
if !absent.is_empty() {
|
|
|
|
println!("Absent Signers (Pubkey):");
|
|
|
|
absent.iter().for_each(|pubkey| println!(" {}", pubkey))
|
|
|
|
}
|
|
|
|
if !bad_sig.is_empty() {
|
|
|
|
println!("Bad Signatures (Pubkey):");
|
|
|
|
bad_sig.iter().for_each(|pubkey| println!(" {}", pubkey))
|
|
|
|
}
|
2019-11-25 21:09:57 -08:00
|
|
|
println!();
|
|
|
|
}
|
2020-04-15 20:51:05 -07:00
|
|
|
|
2020-05-01 17:48:22 -07:00
|
|
|
pub fn write_transaction<W: io::Write>(
|
|
|
|
w: &mut W,
|
2020-04-15 20:51:05 -07:00
|
|
|
transaction: &Transaction,
|
2020-07-01 14:06:40 -06:00
|
|
|
transaction_status: &Option<UiTransactionStatusMeta>,
|
2020-04-15 20:51:05 -07:00
|
|
|
prefix: &str,
|
2020-05-01 17:48:22 -07:00
|
|
|
) -> io::Result<()> {
|
2020-04-15 20:51:05 -07:00
|
|
|
let message = &transaction.message;
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
|
|
|
"{}Recent Blockhash: {:?}",
|
|
|
|
prefix, message.recent_blockhash
|
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
for (signature_index, signature) in transaction.signatures.iter().enumerate() {
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
|
|
|
"{}Signature {}: {:?}",
|
|
|
|
prefix, signature_index, signature
|
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{}{:?}", prefix, message.header)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
for (account_index, account) in message.account_keys.iter().enumerate() {
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{}Account {}: {:?}", prefix, account_index, account)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
|
|
|
let program_pubkey = message.account_keys[instruction.program_id_index as usize];
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{}Instruction {}", prefix, instruction_index)?;
|
|
|
|
writeln!(
|
|
|
|
w,
|
2020-04-15 20:51:05 -07:00
|
|
|
"{} Program: {} ({})",
|
|
|
|
prefix, program_pubkey, instruction.program_id_index
|
2020-05-01 17:48:22 -07:00
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
for (account_index, account) in instruction.accounts.iter().enumerate() {
|
|
|
|
let account_pubkey = message.account_keys[*account as usize];
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
2020-04-15 20:51:05 -07:00
|
|
|
"{} Account {}: {} ({})",
|
|
|
|
prefix, account_index, account_pubkey, account
|
2020-05-01 17:48:22 -07:00
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut raw = true;
|
|
|
|
if program_pubkey == solana_vote_program::id() {
|
|
|
|
if let Ok(vote_instruction) = limited_deserialize::<
|
|
|
|
solana_vote_program::vote_instruction::VoteInstruction,
|
|
|
|
>(&instruction.data)
|
|
|
|
{
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{} {:?}", prefix, vote_instruction)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
raw = false;
|
|
|
|
}
|
|
|
|
} else if program_pubkey == solana_stake_program::id() {
|
|
|
|
if let Ok(stake_instruction) = limited_deserialize::<
|
|
|
|
solana_stake_program::stake_instruction::StakeInstruction,
|
|
|
|
>(&instruction.data)
|
|
|
|
{
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{} {:?}", prefix, stake_instruction)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
raw = false;
|
|
|
|
}
|
|
|
|
} else if program_pubkey == solana_sdk::system_program::id() {
|
|
|
|
if let Ok(system_instruction) = limited_deserialize::<
|
|
|
|
solana_sdk::system_instruction::SystemInstruction,
|
|
|
|
>(&instruction.data)
|
|
|
|
{
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{} {:?}", prefix, system_instruction)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
raw = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if raw {
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{} Data: {:?}", prefix, instruction.data)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(transaction_status) = transaction_status {
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
2020-04-15 20:51:05 -07:00
|
|
|
"{}Status: {}",
|
|
|
|
prefix,
|
|
|
|
match &transaction_status.status {
|
|
|
|
Ok(_) => "Ok".into(),
|
|
|
|
Err(err) => err.to_string(),
|
|
|
|
}
|
2020-05-01 17:48:22 -07:00
|
|
|
)?;
|
2020-05-05 22:10:41 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
2020-09-06 11:10:19 -07:00
|
|
|
"{} Fee: ◎{}",
|
2020-05-05 22:10:41 -07:00
|
|
|
prefix,
|
|
|
|
lamports_to_sol(transaction_status.fee)
|
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
assert_eq!(
|
|
|
|
transaction_status.pre_balances.len(),
|
|
|
|
transaction_status.post_balances.len()
|
|
|
|
);
|
|
|
|
for (i, (pre, post)) in transaction_status
|
|
|
|
.pre_balances
|
|
|
|
.iter()
|
|
|
|
.zip(transaction_status.post_balances.iter())
|
|
|
|
.enumerate()
|
|
|
|
{
|
|
|
|
if pre == post {
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
2020-09-06 11:10:19 -07:00
|
|
|
"{} Account {} balance: ◎{}",
|
2020-04-15 20:51:05 -07:00
|
|
|
prefix,
|
|
|
|
i,
|
|
|
|
lamports_to_sol(*pre)
|
2020-05-01 17:48:22 -07:00
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
} else {
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(
|
|
|
|
w,
|
2020-09-06 11:10:19 -07:00
|
|
|
"{} Account {} balance: ◎{} -> ◎{}",
|
2020-04-15 20:51:05 -07:00
|
|
|
prefix,
|
|
|
|
i,
|
|
|
|
lamports_to_sol(*pre),
|
|
|
|
lamports_to_sol(*post)
|
2020-05-01 17:48:22 -07:00
|
|
|
)?;
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2020-05-01 17:48:22 -07:00
|
|
|
writeln!(w, "{}Status: Unavailable", prefix)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn println_transaction(
|
|
|
|
transaction: &Transaction,
|
2020-07-01 14:06:40 -06:00
|
|
|
transaction_status: &Option<UiTransactionStatusMeta>,
|
2020-05-01 17:48:22 -07:00
|
|
|
prefix: &str,
|
|
|
|
) {
|
|
|
|
let mut w = Vec::new();
|
|
|
|
if write_transaction(&mut w, transaction, transaction_status, prefix).is_ok() {
|
|
|
|
if let Ok(s) = String::from_utf8(w) {
|
|
|
|
print!("{}", s);
|
|
|
|
}
|
2020-04-15 20:51:05 -07:00
|
|
|
}
|
|
|
|
}
|
2020-05-31 23:00:51 -07:00
|
|
|
|
|
|
|
/// Creates a new process bar for processing that will take an unknown amount of time
|
|
|
|
pub fn new_spinner_progress_bar() -> ProgressBar {
|
|
|
|
let progress_bar = ProgressBar::new(42);
|
|
|
|
progress_bar
|
|
|
|
.set_style(ProgressStyle::default_spinner().template("{spinner:.green} {wide_msg}"));
|
|
|
|
progress_bar.enable_steady_tick(100);
|
|
|
|
progress_bar
|
|
|
|
}
|
2020-06-17 23:09:33 -07:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
use solana_sdk::pubkey::Pubkey;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_format_labeled_address() {
|
|
|
|
let pubkey = Pubkey::default().to_string();
|
|
|
|
let mut address_labels = HashMap::new();
|
|
|
|
|
|
|
|
assert_eq!(format_labeled_address(&pubkey, &address_labels), pubkey);
|
|
|
|
|
|
|
|
address_labels.insert(pubkey.to_string(), "Default Address".to_string());
|
|
|
|
assert_eq!(
|
|
|
|
&format_labeled_address(&pubkey, &address_labels),
|
|
|
|
"Default Address (1111..1111)"
|
|
|
|
);
|
|
|
|
|
|
|
|
address_labels.insert(
|
|
|
|
pubkey.to_string(),
|
|
|
|
"abcdefghijklmnopqrstuvwxyz1234567890".to_string(),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
&format_labeled_address(&pubkey, &address_labels),
|
|
|
|
"abcdefghijklmnopqrstuvwxyz12345 (1111..1111)"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|