1#![allow(clippy::to_string_in_format_args)]
2use {
3 crate::{
4 cli_version::CliVersion,
5 display::{
6 build_balance_message, build_balance_message_with_config, format_labeled_address,
7 unix_timestamp_to_string, writeln_name_value, writeln_transaction,
8 BuildBalanceMessageConfig,
9 },
10 QuietDisplay, VerboseDisplay,
11 },
12 chrono::{Local, TimeZone},
13 clap::ArgMatches,
14 console::{style, Emoji},
15 inflector::cases::titlecase::to_title_case,
16 serde::{Deserialize, Serialize},
17 serde_json::{Map, Value},
18 safecoin_account_decoder::parse_token::UiTokenAccount,
19 safecoin_clap_utils::keypair::SignOnly,
20 safecoin_client::rpc_response::{
21 RpcAccountBalance, RpcContactInfo, RpcInflationGovernor, RpcInflationRate, RpcKeyedAccount,
22 RpcSupply, RpcVoteAccountInfo,
23 },
24 solana_sdk::{
25 clock::{Epoch, Slot, UnixTimestamp},
26 epoch_info::EpochInfo,
27 hash::Hash,
28 native_token::lamports_to_sol,
29 pubkey::Pubkey,
30 signature::Signature,
31 stake::state::{Authorized, Lockup},
32 stake_history::StakeHistoryEntry,
33 transaction::{Transaction, TransactionError, VersionedTransaction},
34 },
35 safecoin_transaction_status::{
36 EncodedConfirmedBlock, EncodedTransaction, TransactionConfirmationStatus,
37 UiTransactionStatusMeta,
38 },
39 solana_vote_program::{
40 authorized_voters::AuthorizedVoters,
41 vote_state::{BlockTimestamp, Lockout, MAX_EPOCH_CREDITS_HISTORY, MAX_LOCKOUT_HISTORY},
42 },
43 std::{
44 collections::{BTreeMap, HashMap},
45 fmt,
46 str::FromStr,
47 time::Duration,
48 },
49};
50
51static CHECK_MARK: Emoji = Emoji("✅ ", "");
52static CROSS_MARK: Emoji = Emoji("❌ ", "");
53static WARNING: Emoji = Emoji("⚠️", "!");
54
55#[derive(PartialEq, Eq, Debug)]
56pub enum OutputFormat {
57 Display,
58 Json,
59 JsonCompact,
60 DisplayQuiet,
61 DisplayVerbose,
62}
63
64impl OutputFormat {
65 pub fn formatted_string<T>(&self, item: &T) -> String
66 where
67 T: Serialize + fmt::Display + QuietDisplay + VerboseDisplay,
68 {
69 match self {
70 OutputFormat::Display => format!("{}", item),
71 OutputFormat::DisplayQuiet => {
72 let mut s = String::new();
73 QuietDisplay::write_str(item, &mut s).unwrap();
74 s
75 }
76 OutputFormat::DisplayVerbose => {
77 let mut s = String::new();
78 VerboseDisplay::write_str(item, &mut s).unwrap();
79 s
80 }
81 OutputFormat::Json => serde_json::to_string_pretty(item).unwrap(),
82 OutputFormat::JsonCompact => serde_json::to_value(item).unwrap().to_string(),
83 }
84 }
85
86 pub fn from_matches(matches: &ArgMatches<'_>, output_name: &str, verbose: bool) -> Self {
87 matches
88 .value_of(output_name)
89 .map(|value| match value {
90 "json" => OutputFormat::Json,
91 "json-compact" => OutputFormat::JsonCompact,
92 _ => unreachable!(),
93 })
94 .unwrap_or(if verbose {
95 OutputFormat::DisplayVerbose
96 } else {
97 OutputFormat::Display
98 })
99 }
100}
101
102#[derive(Serialize, Deserialize)]
103pub struct CliAccount {
104 #[serde(flatten)]
105 pub keyed_account: RpcKeyedAccount,
106 #[serde(skip_serializing, skip_deserializing)]
107 pub use_lamports_unit: bool,
108}
109
110impl QuietDisplay for CliAccount {}
111impl VerboseDisplay for CliAccount {}
112
113impl fmt::Display for CliAccount {
114 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115 writeln!(f)?;
116 writeln_name_value(f, "Public Key:", &self.keyed_account.pubkey)?;
117 writeln_name_value(
118 f,
119 "Balance:",
120 &build_balance_message(
121 self.keyed_account.account.lamports,
122 self.use_lamports_unit,
123 true,
124 ),
125 )?;
126 writeln_name_value(f, "Owner:", &self.keyed_account.account.owner)?;
127 writeln_name_value(
128 f,
129 "Executable:",
130 &self.keyed_account.account.executable.to_string(),
131 )?;
132 writeln_name_value(
133 f,
134 "Rent Epoch:",
135 &self.keyed_account.account.rent_epoch.to_string(),
136 )?;
137 Ok(())
138 }
139}
140
141#[derive(Default, Serialize, Deserialize)]
142pub struct CliBlockProduction {
143 pub epoch: Epoch,
144 pub start_slot: Slot,
145 pub end_slot: Slot,
146 pub total_slots: usize,
147 pub total_blocks_produced: usize,
148 pub total_slots_skipped: usize,
149 pub leaders: Vec<CliBlockProductionEntry>,
150 pub individual_slot_status: Vec<CliSlotStatus>,
151 #[serde(skip_serializing)]
152 pub verbose: bool,
153}
154
155impl QuietDisplay for CliBlockProduction {}
156impl VerboseDisplay for CliBlockProduction {}
157
158impl fmt::Display for CliBlockProduction {
159 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
160 writeln!(f)?;
161 writeln!(
162 f,
163 "{}",
164 style(format!(
165 " {:<44} {:>15} {:>15} {:>15} {:>23}",
166 "Identity",
167 "Leader Slots",
168 "Blocks Produced",
169 "Skipped Slots",
170 "Skipped Slot Percentage",
171 ))
172 .bold()
173 )?;
174 for leader in &self.leaders {
175 writeln!(
176 f,
177 " {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
178 leader.identity_pubkey,
179 leader.leader_slots,
180 leader.blocks_produced,
181 leader.skipped_slots,
182 leader.skipped_slots as f64 / leader.leader_slots as f64 * 100.
183 )?;
184 }
185 writeln!(f)?;
186 writeln!(
187 f,
188 " {:<44} {:>15} {:>15} {:>15} {:>22.2}%",
189 format!("Epoch {} total:", self.epoch),
190 self.total_slots,
191 self.total_blocks_produced,
192 self.total_slots_skipped,
193 self.total_slots_skipped as f64 / self.total_slots as f64 * 100.
194 )?;
195 writeln!(
196 f,
197 " (using data from {} slots: {} to {})",
198 self.total_slots, self.start_slot, self.end_slot
199 )?;
200 if self.verbose {
201 writeln!(f)?;
202 writeln!(f)?;
203 writeln!(
204 f,
205 "{}",
206 style(format!(" {:<15} {:<44}", "Slot", "Identity Pubkey")).bold(),
207 )?;
208 for status in &self.individual_slot_status {
209 if status.skipped {
210 writeln!(
211 f,
212 "{}",
213 style(format!(
214 " {:<15} {:<44} SKIPPED",
215 status.slot, status.leader
216 ))
217 .red()
218 )?;
219 } else {
220 writeln!(
221 f,
222 "{}",
223 style(format!(" {:<15} {:<44}", status.slot, status.leader))
224 )?;
225 }
226 }
227 }
228 Ok(())
229 }
230}
231
232#[derive(Default, Serialize, Deserialize)]
233#[serde(rename_all = "camelCase")]
234pub struct CliBlockProductionEntry {
235 pub identity_pubkey: String,
236 pub leader_slots: u64,
237 pub blocks_produced: u64,
238 pub skipped_slots: u64,
239}
240
241#[derive(Default, Serialize, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct CliSlotStatus {
244 pub slot: Slot,
245 pub leader: String,
246 pub skipped: bool,
247}
248
249#[derive(Serialize, Deserialize)]
250#[serde(rename_all = "camelCase")]
251pub struct CliEpochInfo {
252 #[serde(flatten)]
253 pub epoch_info: EpochInfo,
254 pub epoch_completed_percent: f64,
255 #[serde(skip)]
256 pub average_slot_time_ms: u64,
257 #[serde(skip)]
258 pub start_block_time: Option<UnixTimestamp>,
259 #[serde(skip)]
260 pub current_block_time: Option<UnixTimestamp>,
261}
262
263impl QuietDisplay for CliEpochInfo {}
264impl VerboseDisplay for CliEpochInfo {}
265
266impl fmt::Display for CliEpochInfo {
267 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
268 writeln!(f)?;
269 writeln_name_value(
270 f,
271 "Block height:",
272 &self.epoch_info.block_height.to_string(),
273 )?;
274 writeln_name_value(f, "Slot:", &self.epoch_info.absolute_slot.to_string())?;
275 writeln_name_value(f, "Epoch:", &self.epoch_info.epoch.to_string())?;
276 if let Some(transaction_count) = &self.epoch_info.transaction_count {
277 writeln_name_value(f, "Transaction Count:", &transaction_count.to_string())?;
278 }
279 let start_slot = self.epoch_info.absolute_slot - self.epoch_info.slot_index;
280 let end_slot = start_slot + self.epoch_info.slots_in_epoch;
281 writeln_name_value(
282 f,
283 "Epoch Slot Range:",
284 &format!("[{}..{})", start_slot, end_slot),
285 )?;
286 writeln_name_value(
287 f,
288 "Epoch Completed Percent:",
289 &format!("{:>3.3}%", self.epoch_completed_percent),
290 )?;
291 let remaining_slots_in_epoch = self.epoch_info.slots_in_epoch - self.epoch_info.slot_index;
292 writeln_name_value(
293 f,
294 "Epoch Completed Slots:",
295 &format!(
296 "{}/{} ({} remaining)",
297 self.epoch_info.slot_index,
298 self.epoch_info.slots_in_epoch,
299 remaining_slots_in_epoch
300 ),
301 )?;
302 let (time_elapsed, annotation) = if let (Some(start_block_time), Some(current_block_time)) =
303 (self.start_block_time, self.current_block_time)
304 {
305 (
306 Duration::from_secs((current_block_time - start_block_time) as u64),
307 None,
308 )
309 } else {
310 (
311 slot_to_duration(self.epoch_info.slot_index, self.average_slot_time_ms),
312 Some("* estimated based on current slot durations"),
313 )
314 };
315 let time_remaining = slot_to_duration(remaining_slots_in_epoch, self.average_slot_time_ms);
316 writeln_name_value(
317 f,
318 "Epoch Completed Time:",
319 &format!(
320 "{}{}/{} ({} remaining)",
321 humantime::format_duration(time_elapsed),
322 if annotation.is_some() { "*" } else { "" },
323 humantime::format_duration(time_elapsed + time_remaining),
324 humantime::format_duration(time_remaining),
325 ),
326 )?;
327 if let Some(annotation) = annotation {
328 writeln!(f)?;
329 writeln!(f, "{}", annotation)?;
330 }
331 Ok(())
332 }
333}
334
335fn slot_to_duration(slot: Slot, slot_time_ms: u64) -> Duration {
336 Duration::from_secs((slot * slot_time_ms) / 1000)
337}
338
339#[derive(Serialize, Deserialize, Default)]
340#[serde(rename_all = "camelCase")]
341pub struct CliValidatorsStakeByVersion {
342 pub current_validators: usize,
343 pub delinquent_validators: usize,
344 pub current_active_stake: u64,
345 pub delinquent_active_stake: u64,
346}
347
348#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
349pub enum CliValidatorsSortOrder {
350 Delinquent,
351 Commission,
352 EpochCredits,
353 Identity,
354 LastVote,
355 Root,
356 SkipRate,
357 Stake,
358 VoteAccount,
359 Version,
360}
361
362#[derive(Serialize, Deserialize)]
363#[serde(rename_all = "camelCase")]
364pub struct CliValidators {
365 pub total_active_stake: u64,
366 pub total_current_stake: u64,
367 pub total_delinquent_stake: u64,
368 pub validators: Vec<CliValidator>,
369 pub average_skip_rate: f64,
370 pub average_stake_weighted_skip_rate: f64,
371 #[serde(skip_serializing)]
372 pub validators_sort_order: CliValidatorsSortOrder,
373 #[serde(skip_serializing)]
374 pub validators_reverse_sort: bool,
375 #[serde(skip_serializing)]
376 pub number_validators: bool,
377 pub stake_by_version: BTreeMap<CliVersion, CliValidatorsStakeByVersion>,
378 #[serde(skip_serializing)]
379 pub use_lamports_unit: bool,
380}
381
382impl QuietDisplay for CliValidators {}
383impl VerboseDisplay for CliValidators {}
384
385impl fmt::Display for CliValidators {
386 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
387 fn write_vote_account(
388 f: &mut fmt::Formatter,
389 validator: &CliValidator,
390 total_active_stake: u64,
391 use_lamports_unit: bool,
392 highest_last_vote: u64,
393 highest_root: u64,
394 ) -> fmt::Result {
395 fn non_zero_or_dash(v: u64, max_v: u64) -> String {
396 if v == 0 {
397 " - ".into()
398 } else if v == max_v {
399 format!("{:>9} ( 0)", v)
400 } else if v > max_v.saturating_sub(100) {
401 format!("{:>9} ({:>3})", v, -(max_v.saturating_sub(v) as isize))
402 } else {
403 format!("{:>9} ", v)
404 }
405 }
406
407 writeln!(
408 f,
409 "{} {:<44} {:<44} {:>3}% {:>14} {:>14} {:>7} {:>8} {:>7} {:>22} ({:.2}%)",
410 if validator.delinquent {
411 WARNING.to_string()
412 } else {
413 "\u{a0}".to_string()
414 },
415 validator.identity_pubkey,
416 validator.vote_account_pubkey,
417 validator.commission,
418 non_zero_or_dash(validator.last_vote, highest_last_vote),
419 non_zero_or_dash(validator.root_slot, highest_root),
420 if let Some(skip_rate) = validator.skip_rate {
421 format!("{:.2}%", skip_rate)
422 } else {
423 "- ".to_string()
424 },
425 validator.epoch_credits,
426 validator.version.to_string(),
428 build_balance_message_with_config(
429 validator.activated_stake,
430 &BuildBalanceMessageConfig {
431 use_lamports_unit,
432 trim_trailing_zeros: false,
433 ..BuildBalanceMessageConfig::default()
434 }
435 ),
436 100. * validator.activated_stake as f64 / total_active_stake as f64,
437 )
438 }
439
440 let padding = if self.number_validators {
441 ((self.validators.len() + 1) as f64).log10().floor() as usize + 1
442 } else {
443 0
444 };
445 let header = style(format!(
446 "{:padding$} {:<44} {:<38} {} {} {} {} {} {} {:>22}",
447 " ",
448 "Identity",
449 "Vote Account",
450 "Commission",
451 "Last Vote ",
452 "Root Slot ",
453 "Skip Rate",
454 "Credits",
455 "Version",
456 "Active Stake",
457 padding = padding + 2
458 ))
459 .bold();
460 writeln!(f, "{}", header)?;
461
462 let mut sorted_validators = self.validators.clone();
463 match self.validators_sort_order {
464 CliValidatorsSortOrder::Delinquent => {
465 sorted_validators.sort_by_key(|a| a.delinquent);
466 }
467 CliValidatorsSortOrder::Commission => {
468 sorted_validators.sort_by_key(|a| a.commission);
469 }
470 CliValidatorsSortOrder::EpochCredits => {
471 sorted_validators.sort_by_key(|a| a.epoch_credits);
472 }
473 CliValidatorsSortOrder::Identity => {
474 sorted_validators.sort_by(|a, b| a.identity_pubkey.cmp(&b.identity_pubkey));
475 }
476 CliValidatorsSortOrder::LastVote => {
477 sorted_validators.sort_by_key(|a| a.last_vote);
478 }
479 CliValidatorsSortOrder::Root => {
480 sorted_validators.sort_by_key(|a| a.root_slot);
481 }
482 CliValidatorsSortOrder::VoteAccount => {
483 sorted_validators.sort_by(|a, b| a.vote_account_pubkey.cmp(&b.vote_account_pubkey));
484 }
485 CliValidatorsSortOrder::SkipRate => {
486 sorted_validators.sort_by(|a, b| {
487 use std::cmp::Ordering;
488 match (a.skip_rate, b.skip_rate) {
489 (None, None) => Ordering::Equal,
490 (None, Some(_)) => Ordering::Greater,
491 (Some(_), None) => Ordering::Less,
492 (Some(a), Some(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Equal),
493 }
494 });
495 }
496 CliValidatorsSortOrder::Stake => {
497 sorted_validators.sort_by_key(|a| a.activated_stake);
498 }
499 CliValidatorsSortOrder::Version => {
500 sorted_validators.sort_by(|a, b| {
501 use std::cmp::Ordering;
502 match a.version.cmp(&b.version) {
503 Ordering::Equal => a.activated_stake.cmp(&b.activated_stake),
504 ordering => ordering,
505 }
506 });
507 }
508 }
509
510 if self.validators_reverse_sort {
511 sorted_validators.reverse();
512 }
513
514 let highest_root = sorted_validators
515 .iter()
516 .map(|v| v.root_slot)
517 .max()
518 .unwrap_or_default();
519 let highest_last_vote = sorted_validators
520 .iter()
521 .map(|v| v.last_vote)
522 .max()
523 .unwrap_or_default();
524
525 for (i, validator) in sorted_validators.iter().enumerate() {
526 if padding > 0 {
527 let num = if self.validators_reverse_sort {
528 i + 1
529 } else {
530 sorted_validators.len() - i
531 };
532 write!(f, "{:padding$} ", num, padding = padding)?;
533 }
534 write_vote_account(
535 f,
536 validator,
537 self.total_active_stake,
538 self.use_lamports_unit,
539 highest_last_vote,
540 highest_root,
541 )?;
542 }
543
544 if self.validators.len() > 100 {
546 writeln!(f, "{}", header)?;
547 }
548
549 writeln!(f)?;
550 writeln_name_value(
551 f,
552 "Average Stake-Weighted Skip Rate:",
553 &format!("{:.2}%", self.average_stake_weighted_skip_rate,),
554 )?;
555 writeln_name_value(
556 f,
557 "Average Unweighted Skip Rate: ",
558 &format!("{:.2}%", self.average_skip_rate),
559 )?;
560
561 writeln!(f)?;
562 writeln_name_value(
563 f,
564 "Active Stake:",
565 &build_balance_message(self.total_active_stake, self.use_lamports_unit, true),
566 )?;
567 if self.total_delinquent_stake > 0 {
568 writeln_name_value(
569 f,
570 "Current Stake:",
571 &format!(
572 "{} ({:0.2}%)",
573 &build_balance_message(self.total_current_stake, self.use_lamports_unit, true),
574 100. * self.total_current_stake as f64 / self.total_active_stake as f64
575 ),
576 )?;
577 writeln_name_value(
578 f,
579 "Delinquent Stake:",
580 &format!(
581 "{} ({:0.2}%)",
582 &build_balance_message(
583 self.total_delinquent_stake,
584 self.use_lamports_unit,
585 true
586 ),
587 100. * self.total_delinquent_stake as f64 / self.total_active_stake as f64
588 ),
589 )?;
590 }
591
592 writeln!(f)?;
593 writeln!(f, "{}", style("Stake By Version:").bold())?;
594 for (version, info) in self.stake_by_version.iter().rev() {
595 writeln!(
596 f,
597 "{:<7} - {:4} current validators ({:>5.2}%){}",
598 version.to_string(),
600 info.current_validators,
601 100. * info.current_active_stake as f64 / self.total_active_stake as f64,
602 if info.delinquent_validators > 0 {
603 format!(
604 " {:3} delinquent validators ({:>5.2}%)",
605 info.delinquent_validators,
606 100. * info.delinquent_active_stake as f64 / self.total_active_stake as f64
607 )
608 } else {
609 "".to_string()
610 },
611 )?;
612 }
613
614 Ok(())
615 }
616}
617
618#[derive(Serialize, Deserialize, Clone)]
619#[serde(rename_all = "camelCase")]
620pub struct CliValidator {
621 pub identity_pubkey: String,
622 pub vote_account_pubkey: String,
623 pub commission: u8,
624 pub last_vote: u64,
625 pub root_slot: u64,
626 pub credits: u64, pub epoch_credits: u64, pub activated_stake: u64,
629 pub version: CliVersion,
630 pub delinquent: bool,
631 pub skip_rate: Option<f64>,
632}
633
634impl CliValidator {
635 pub fn new(
636 vote_account: &RpcVoteAccountInfo,
637 current_epoch: Epoch,
638 version: CliVersion,
639 skip_rate: Option<f64>,
640 address_labels: &HashMap<String, String>,
641 ) -> Self {
642 Self::_new(
643 vote_account,
644 current_epoch,
645 version,
646 skip_rate,
647 address_labels,
648 false,
649 )
650 }
651
652 pub fn new_delinquent(
653 vote_account: &RpcVoteAccountInfo,
654 current_epoch: Epoch,
655 version: CliVersion,
656 skip_rate: Option<f64>,
657 address_labels: &HashMap<String, String>,
658 ) -> Self {
659 Self::_new(
660 vote_account,
661 current_epoch,
662 version,
663 skip_rate,
664 address_labels,
665 true,
666 )
667 }
668
669 fn _new(
670 vote_account: &RpcVoteAccountInfo,
671 current_epoch: Epoch,
672 version: CliVersion,
673 skip_rate: Option<f64>,
674 address_labels: &HashMap<String, String>,
675 delinquent: bool,
676 ) -> Self {
677 let (credits, epoch_credits) = vote_account
678 .epoch_credits
679 .iter()
680 .find_map(|(epoch, credits, pre_credits)| {
681 if *epoch == current_epoch {
682 Some((*credits, credits.saturating_sub(*pre_credits)))
683 } else {
684 None
685 }
686 })
687 .unwrap_or((0, 0));
688 Self {
689 identity_pubkey: format_labeled_address(&vote_account.node_pubkey, address_labels),
690 vote_account_pubkey: format_labeled_address(&vote_account.vote_pubkey, address_labels),
691 commission: vote_account.commission,
692 last_vote: vote_account.last_vote,
693 root_slot: vote_account.root_slot,
694 credits,
695 epoch_credits,
696 activated_stake: vote_account.activated_stake,
697 version,
698 delinquent,
699 skip_rate,
700 }
701 }
702}
703
704#[derive(Default, Serialize, Deserialize)]
705#[serde(rename_all = "camelCase")]
706pub struct CliNonceAccount {
707 pub balance: u64,
708 pub minimum_balance_for_rent_exemption: u64,
709 pub nonce: Option<String>,
710 pub lamports_per_signature: Option<u64>,
711 pub authority: Option<String>,
712 #[serde(skip_serializing)]
713 pub use_lamports_unit: bool,
714}
715
716impl QuietDisplay for CliNonceAccount {}
717impl VerboseDisplay for CliNonceAccount {}
718
719impl fmt::Display for CliNonceAccount {
720 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
721 writeln!(
722 f,
723 "Balance: {}",
724 build_balance_message(self.balance, self.use_lamports_unit, true)
725 )?;
726 writeln!(
727 f,
728 "Minimum Balance Required: {}",
729 build_balance_message(
730 self.minimum_balance_for_rent_exemption,
731 self.use_lamports_unit,
732 true
733 )
734 )?;
735 let nonce = self.nonce.as_deref().unwrap_or("uninitialized");
736 writeln!(f, "Nonce blockhash: {}", nonce)?;
737 if let Some(fees) = self.lamports_per_signature {
738 writeln!(f, "Fee: {} lamports per signature", fees)?;
739 } else {
740 writeln!(f, "Fees: uninitialized")?;
741 }
742 let authority = self.authority.as_deref().unwrap_or("uninitialized");
743 writeln!(f, "Authority: {}", authority)
744 }
745}
746
747#[derive(Serialize, Deserialize)]
748pub struct CliStakeVec(Vec<CliKeyedStakeState>);
749
750impl CliStakeVec {
751 pub fn new(list: Vec<CliKeyedStakeState>) -> Self {
752 Self(list)
753 }
754}
755
756impl QuietDisplay for CliStakeVec {}
757impl VerboseDisplay for CliStakeVec {
758 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
759 for state in &self.0 {
760 writeln!(w)?;
761 VerboseDisplay::write_str(state, w)?;
762 }
763 Ok(())
764 }
765}
766
767impl fmt::Display for CliStakeVec {
768 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
769 for state in &self.0 {
770 writeln!(f)?;
771 write!(f, "{}", state)?;
772 }
773 Ok(())
774 }
775}
776
777#[derive(Serialize, Deserialize)]
778#[serde(rename_all = "camelCase")]
779pub struct CliKeyedStakeState {
780 pub stake_pubkey: String,
781 #[serde(flatten)]
782 pub stake_state: CliStakeState,
783}
784
785impl QuietDisplay for CliKeyedStakeState {}
786impl VerboseDisplay for CliKeyedStakeState {
787 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
788 writeln!(w, "Stake Pubkey: {}", self.stake_pubkey)?;
789 VerboseDisplay::write_str(&self.stake_state, w)
790 }
791}
792
793impl fmt::Display for CliKeyedStakeState {
794 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
795 writeln!(f, "Stake Pubkey: {}", self.stake_pubkey)?;
796 write!(f, "{}", self.stake_state)
797 }
798}
799
800#[derive(Serialize, Deserialize)]
801#[serde(rename_all = "camelCase")]
802pub struct CliEpochReward {
803 pub epoch: Epoch,
804 pub effective_slot: Slot,
805 pub amount: u64, pub post_balance: u64, pub percent_change: f64,
808 pub apr: Option<f64>,
809 pub commission: Option<u8>,
810}
811
812#[derive(Serialize, Deserialize)]
813#[serde(rename_all = "camelCase")]
814pub struct CliKeyedEpochReward {
815 pub address: String,
816 pub reward: Option<CliEpochReward>,
817}
818
819#[derive(Serialize, Deserialize)]
820#[serde(rename_all = "camelCase")]
821pub struct CliEpochRewardshMetadata {
822 pub epoch: Epoch,
823 pub effective_slot: Slot,
824 pub block_time: UnixTimestamp,
825}
826
827#[derive(Serialize, Deserialize)]
828#[serde(rename_all = "camelCase")]
829pub struct CliKeyedEpochRewards {
830 #[serde(flatten, skip_serializing_if = "Option::is_none")]
831 pub epoch_metadata: Option<CliEpochRewardshMetadata>,
832 pub rewards: Vec<CliKeyedEpochReward>,
833}
834
835impl QuietDisplay for CliKeyedEpochRewards {}
836impl VerboseDisplay for CliKeyedEpochRewards {}
837
838impl fmt::Display for CliKeyedEpochRewards {
839 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
840 if self.rewards.is_empty() {
841 writeln!(f, "No rewards found in epoch")?;
842 return Ok(());
843 }
844
845 if let Some(metadata) = &self.epoch_metadata {
846 writeln!(f, "Epoch: {}", metadata.epoch)?;
847 writeln!(f, "Reward Slot: {}", metadata.effective_slot)?;
848 let timestamp = metadata.block_time;
849 writeln!(f, "Block Time: {}", unix_timestamp_to_string(timestamp))?;
850 }
851 writeln!(f, "Epoch Rewards:")?;
852 writeln!(
853 f,
854 " {:<44} {:<18} {:<18} {:>14} {:>14} {:>10}",
855 "Address", "Amount", "New Balance", "Percent Change", "APR", "Commission"
856 )?;
857 for keyed_reward in &self.rewards {
858 match &keyed_reward.reward {
859 Some(reward) => {
860 writeln!(
861 f,
862 " {:<44} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}",
863 keyed_reward.address,
864 lamports_to_sol(reward.amount),
865 lamports_to_sol(reward.post_balance),
866 reward.percent_change,
867 reward
868 .apr
869 .map(|apr| format!("{:.2}%", apr))
870 .unwrap_or_default(),
871 reward
872 .commission
873 .map(|commission| format!("{}%", commission))
874 .unwrap_or_else(|| "-".to_string())
875 )?;
876 }
877 None => {
878 writeln!(f, " {:<44} No rewards in epoch", keyed_reward.address,)?;
879 }
880 }
881 }
882 Ok(())
883 }
884}
885
886fn show_votes_and_credits(
887 f: &mut fmt::Formatter,
888 votes: &[CliLockout],
889 epoch_voting_history: &[CliEpochVotingHistory],
890) -> fmt::Result {
891 if votes.is_empty() {
892 return Ok(());
893 }
894
895 let newest_history_entry = epoch_voting_history.iter().rev().next();
897
898 writeln!(
899 f,
900 "{} Votes (using {}/{} entries):",
901 (if newest_history_entry.is_none() {
902 "All"
903 } else {
904 "Recent"
905 }),
906 votes.len(),
907 MAX_LOCKOUT_HISTORY
908 )?;
909
910 for vote in votes.iter().rev() {
911 writeln!(
912 f,
913 "- slot: {} (confirmation count: {})",
914 vote.slot, vote.confirmation_count
915 )?;
916 }
917 if let Some(newest) = newest_history_entry {
918 writeln!(
919 f,
920 "- ... (truncated {} rooted votes, which have been credited)",
921 newest.credits
922 )?;
923 }
924
925 if !epoch_voting_history.is_empty() {
926 writeln!(
927 f,
928 "{} Epoch Voting History (using {}/{} entries):",
929 (if epoch_voting_history.len() < MAX_EPOCH_CREDITS_HISTORY {
930 "All"
931 } else {
932 "Recent"
933 }),
934 epoch_voting_history.len(),
935 MAX_EPOCH_CREDITS_HISTORY
936 )?;
937 writeln!(
938 f,
939 "* missed credits include slots unavailable to vote on due to delinquent leaders",
940 )?;
941 }
942
943 for entry in epoch_voting_history.iter().rev() {
944 writeln!(
945 f, "- epoch: {}",
947 entry.epoch
948 )?;
949 writeln!(
950 f,
951 " credits range: ({}..{}]",
952 entry.prev_credits, entry.credits
953 )?;
954 writeln!(
955 f,
956 " credits/slots: {}/{}",
957 entry.credits_earned, entry.slots_in_epoch
958 )?;
959 }
960 if let Some(oldest) = epoch_voting_history.iter().next() {
961 if oldest.prev_credits > 0 {
962 let count = oldest.prev_credits;
967
968 writeln!(
969 f,
970 "- ... (omitting {} past rooted votes, which have already been credited)",
971 count
972 )?;
973 }
974 }
975
976 Ok(())
977}
978
979fn show_epoch_rewards(
980 f: &mut fmt::Formatter,
981 epoch_rewards: &Option<Vec<CliEpochReward>>,
982) -> fmt::Result {
983 if let Some(epoch_rewards) = epoch_rewards {
984 if epoch_rewards.is_empty() {
985 return Ok(());
986 }
987
988 writeln!(f, "Epoch Rewards:")?;
989 writeln!(
990 f,
991 " {:<6} {:<11} {:<18} {:<18} {:>14} {:>14} {:>10}",
992 "Epoch", "Reward Slot", "Amount", "New Balance", "Percent Change", "APR", "Commission"
993 )?;
994 for reward in epoch_rewards {
995 writeln!(
996 f,
997 " {:<6} {:<11} ◎{:<17.9} ◎{:<17.9} {:>13.9}% {:>14} {:>10}",
998 reward.epoch,
999 reward.effective_slot,
1000 lamports_to_sol(reward.amount),
1001 lamports_to_sol(reward.post_balance),
1002 reward.percent_change,
1003 reward
1004 .apr
1005 .map(|apr| format!("{:.2}%", apr))
1006 .unwrap_or_default(),
1007 reward
1008 .commission
1009 .map(|commission| format!("{}%", commission))
1010 .unwrap_or_else(|| "-".to_string())
1011 )?;
1012 }
1013 }
1014 Ok(())
1015}
1016
1017#[derive(Default, Serialize, Deserialize)]
1018#[serde(rename_all = "camelCase")]
1019pub struct CliStakeState {
1020 pub stake_type: CliStakeType,
1021 pub account_balance: u64,
1022 #[serde(skip_serializing_if = "Option::is_none")]
1023 pub credits_observed: Option<u64>,
1024 #[serde(skip_serializing_if = "Option::is_none")]
1025 pub delegated_stake: Option<u64>,
1026 #[serde(skip_serializing_if = "Option::is_none")]
1027 pub delegated_vote_account_address: Option<String>,
1028 #[serde(skip_serializing_if = "Option::is_none")]
1029 pub activation_epoch: Option<Epoch>,
1030 #[serde(skip_serializing_if = "Option::is_none")]
1031 pub deactivation_epoch: Option<Epoch>,
1032 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1033 pub authorized: Option<CliAuthorized>,
1034 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1035 pub lockup: Option<CliLockup>,
1036 #[serde(skip_serializing)]
1037 pub use_lamports_unit: bool,
1038 #[serde(skip_serializing)]
1039 pub current_epoch: Epoch,
1040 #[serde(skip_serializing_if = "Option::is_none")]
1041 pub rent_exempt_reserve: Option<u64>,
1042 #[serde(skip_serializing_if = "Option::is_none")]
1043 pub active_stake: Option<u64>,
1044 #[serde(skip_serializing_if = "Option::is_none")]
1045 pub activating_stake: Option<u64>,
1046 #[serde(skip_serializing_if = "Option::is_none")]
1047 pub deactivating_stake: Option<u64>,
1048 #[serde(skip_serializing_if = "Option::is_none")]
1049 pub epoch_rewards: Option<Vec<CliEpochReward>>,
1050}
1051
1052impl QuietDisplay for CliStakeState {}
1053impl VerboseDisplay for CliStakeState {
1054 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
1055 write!(w, "{}", self)?;
1056 if let Some(credits) = self.credits_observed {
1057 writeln!(w, "Credits Observed: {}", credits)?;
1058 }
1059 Ok(())
1060 }
1061}
1062
1063impl fmt::Display for CliStakeState {
1064 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1065 fn show_authorized(f: &mut fmt::Formatter, authorized: &CliAuthorized) -> fmt::Result {
1066 writeln!(f, "Stake Authority: {}", authorized.staker)?;
1067 writeln!(f, "Withdraw Authority: {}", authorized.withdrawer)?;
1068 Ok(())
1069 }
1070 fn show_lockup(f: &mut fmt::Formatter, lockup: Option<&CliLockup>) -> fmt::Result {
1071 if let Some(lockup) = lockup {
1072 if lockup.unix_timestamp != UnixTimestamp::default() {
1073 writeln!(
1074 f,
1075 "Lockup Timestamp: {}",
1076 unix_timestamp_to_string(lockup.unix_timestamp)
1077 )?;
1078 }
1079 if lockup.epoch != Epoch::default() {
1080 writeln!(f, "Lockup Epoch: {}", lockup.epoch)?;
1081 }
1082 writeln!(f, "Lockup Custodian: {}", lockup.custodian)?;
1083 }
1084 Ok(())
1085 }
1086
1087 writeln!(
1088 f,
1089 "Balance: {}",
1090 build_balance_message(self.account_balance, self.use_lamports_unit, true)
1091 )?;
1092
1093 if let Some(rent_exempt_reserve) = self.rent_exempt_reserve {
1094 writeln!(
1095 f,
1096 "Rent Exempt Reserve: {}",
1097 build_balance_message(rent_exempt_reserve, self.use_lamports_unit, true)
1098 )?;
1099 }
1100
1101 match self.stake_type {
1102 CliStakeType::RewardsPool => writeln!(f, "Stake account is a rewards pool")?,
1103 CliStakeType::Uninitialized => writeln!(f, "Stake account is uninitialized")?,
1104 CliStakeType::Initialized => {
1105 writeln!(f, "Stake account is undelegated")?;
1106 show_authorized(f, self.authorized.as_ref().unwrap())?;
1107 show_lockup(f, self.lockup.as_ref())?;
1108 }
1109 CliStakeType::Stake => {
1110 let show_delegation = {
1111 self.active_stake.is_some()
1112 || self.activating_stake.is_some()
1113 || self.deactivating_stake.is_some()
1114 || self
1115 .deactivation_epoch
1116 .map(|de| de > self.current_epoch)
1117 .unwrap_or(true)
1118 };
1119 if show_delegation {
1120 let delegated_stake = self.delegated_stake.unwrap();
1121 writeln!(
1122 f,
1123 "Delegated Stake: {}",
1124 build_balance_message(delegated_stake, self.use_lamports_unit, true)
1125 )?;
1126 if self
1127 .deactivation_epoch
1128 .map(|d| self.current_epoch <= d)
1129 .unwrap_or(true)
1130 {
1131 let active_stake = self.active_stake.unwrap_or(0);
1132 writeln!(
1133 f,
1134 "Active Stake: {}",
1135 build_balance_message(active_stake, self.use_lamports_unit, true),
1136 )?;
1137 let activating_stake = self.activating_stake.or_else(|| {
1138 if self.active_stake.is_none() {
1139 Some(delegated_stake)
1140 } else {
1141 None
1142 }
1143 });
1144 if let Some(activating_stake) = activating_stake {
1145 writeln!(
1146 f,
1147 "Activating Stake: {}",
1148 build_balance_message(
1149 activating_stake,
1150 self.use_lamports_unit,
1151 true
1152 ),
1153 )?;
1154 writeln!(
1155 f,
1156 "Stake activates starting from epoch: {}",
1157 self.activation_epoch.unwrap()
1158 )?;
1159 }
1160 }
1161
1162 if let Some(deactivation_epoch) = self.deactivation_epoch {
1163 if self.current_epoch > deactivation_epoch {
1164 let deactivating_stake = self.deactivating_stake.or(self.active_stake);
1165 if let Some(deactivating_stake) = deactivating_stake {
1166 writeln!(
1167 f,
1168 "Inactive Stake: {}",
1169 build_balance_message(
1170 delegated_stake - deactivating_stake,
1171 self.use_lamports_unit,
1172 true
1173 ),
1174 )?;
1175 writeln!(
1176 f,
1177 "Deactivating Stake: {}",
1178 build_balance_message(
1179 deactivating_stake,
1180 self.use_lamports_unit,
1181 true
1182 ),
1183 )?;
1184 }
1185 }
1186 writeln!(
1187 f,
1188 "Stake deactivates starting from epoch: {}",
1189 deactivation_epoch
1190 )?;
1191 }
1192 if let Some(delegated_vote_account_address) =
1193 &self.delegated_vote_account_address
1194 {
1195 writeln!(
1196 f,
1197 "Delegated Vote Account Address: {}",
1198 delegated_vote_account_address
1199 )?;
1200 }
1201 } else {
1202 writeln!(f, "Stake account is undelegated")?;
1203 }
1204 show_authorized(f, self.authorized.as_ref().unwrap())?;
1205 show_lockup(f, self.lockup.as_ref())?;
1206 show_epoch_rewards(f, &self.epoch_rewards)?
1207 }
1208 }
1209 Ok(())
1210 }
1211}
1212
1213#[derive(Serialize, Deserialize, PartialEq, Eq)]
1214pub enum CliStakeType {
1215 Stake,
1216 RewardsPool,
1217 Uninitialized,
1218 Initialized,
1219}
1220
1221impl Default for CliStakeType {
1222 fn default() -> Self {
1223 Self::Uninitialized
1224 }
1225}
1226
1227#[derive(Serialize, Deserialize)]
1228#[serde(rename_all = "camelCase")]
1229pub struct CliStakeHistory {
1230 pub entries: Vec<CliStakeHistoryEntry>,
1231 #[serde(skip_serializing)]
1232 pub use_lamports_unit: bool,
1233}
1234
1235impl QuietDisplay for CliStakeHistory {}
1236impl VerboseDisplay for CliStakeHistory {}
1237
1238impl fmt::Display for CliStakeHistory {
1239 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1240 writeln!(f)?;
1241 writeln!(
1242 f,
1243 "{}",
1244 style(format!(
1245 " {:<5} {:>20} {:>20} {:>20}",
1246 "Epoch", "Effective Stake", "Activating Stake", "Deactivating Stake",
1247 ))
1248 .bold()
1249 )?;
1250 let config = BuildBalanceMessageConfig {
1251 use_lamports_unit: self.use_lamports_unit,
1252 show_unit: false,
1253 trim_trailing_zeros: false,
1254 };
1255 for entry in &self.entries {
1256 writeln!(
1257 f,
1258 " {:>5} {:>20} {:>20} {:>20} {}",
1259 entry.epoch,
1260 build_balance_message_with_config(entry.effective_stake, &config),
1261 build_balance_message_with_config(entry.activating_stake, &config),
1262 build_balance_message_with_config(entry.deactivating_stake, &config),
1263 if self.use_lamports_unit {
1264 "lamports"
1265 } else {
1266 "SAFE"
1267 }
1268 )?;
1269 }
1270 Ok(())
1271 }
1272}
1273
1274impl From<&(Epoch, StakeHistoryEntry)> for CliStakeHistoryEntry {
1275 fn from((epoch, entry): &(Epoch, StakeHistoryEntry)) -> Self {
1276 Self {
1277 epoch: *epoch,
1278 effective_stake: entry.effective,
1279 activating_stake: entry.activating,
1280 deactivating_stake: entry.deactivating,
1281 }
1282 }
1283}
1284
1285#[derive(Serialize, Deserialize)]
1286#[serde(rename_all = "camelCase")]
1287pub struct CliStakeHistoryEntry {
1288 pub epoch: Epoch,
1289 pub effective_stake: u64,
1290 pub activating_stake: u64,
1291 pub deactivating_stake: u64,
1292}
1293
1294#[derive(Serialize, Deserialize)]
1295#[serde(rename_all = "camelCase")]
1296pub struct CliAuthorized {
1297 pub staker: String,
1298 pub withdrawer: String,
1299}
1300
1301impl From<&Authorized> for CliAuthorized {
1302 fn from(authorized: &Authorized) -> Self {
1303 Self {
1304 staker: authorized.staker.to_string(),
1305 withdrawer: authorized.withdrawer.to_string(),
1306 }
1307 }
1308}
1309
1310#[derive(Serialize, Deserialize)]
1311#[serde(rename_all = "camelCase")]
1312pub struct CliLockup {
1313 pub unix_timestamp: UnixTimestamp,
1314 pub epoch: Epoch,
1315 pub custodian: String,
1316}
1317
1318impl From<&Lockup> for CliLockup {
1319 fn from(lockup: &Lockup) -> Self {
1320 Self {
1321 unix_timestamp: lockup.unix_timestamp,
1322 epoch: lockup.epoch,
1323 custodian: lockup.custodian.to_string(),
1324 }
1325 }
1326}
1327
1328#[derive(Serialize, Deserialize)]
1329pub struct CliValidatorInfoVec(Vec<CliValidatorInfo>);
1330
1331impl CliValidatorInfoVec {
1332 pub fn new(list: Vec<CliValidatorInfo>) -> Self {
1333 Self(list)
1334 }
1335}
1336
1337impl QuietDisplay for CliValidatorInfoVec {}
1338impl VerboseDisplay for CliValidatorInfoVec {}
1339
1340impl fmt::Display for CliValidatorInfoVec {
1341 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1342 if self.0.is_empty() {
1343 writeln!(f, "No validator info accounts found")?;
1344 }
1345 for validator_info in &self.0 {
1346 writeln!(f)?;
1347 write!(f, "{}", validator_info)?;
1348 }
1349 Ok(())
1350 }
1351}
1352
1353#[derive(Serialize, Deserialize)]
1354#[serde(rename_all = "camelCase")]
1355pub struct CliValidatorInfo {
1356 pub identity_pubkey: String,
1357 pub info_pubkey: String,
1358 pub info: Map<String, Value>,
1359}
1360
1361impl QuietDisplay for CliValidatorInfo {}
1362impl VerboseDisplay for CliValidatorInfo {}
1363
1364impl fmt::Display for CliValidatorInfo {
1365 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1366 writeln_name_value(f, "Validator Identity:", &self.identity_pubkey)?;
1367 writeln_name_value(f, " Info Address:", &self.info_pubkey)?;
1368 for (key, value) in self.info.iter() {
1369 writeln_name_value(
1370 f,
1371 &format!(" {}:", to_title_case(key)),
1372 value.as_str().unwrap_or("?"),
1373 )?;
1374 }
1375 Ok(())
1376 }
1377}
1378
1379#[derive(Serialize, Deserialize)]
1380#[serde(rename_all = "camelCase")]
1381pub struct CliVoteAccount {
1382 pub account_balance: u64,
1383 pub validator_identity: String,
1384 #[serde(flatten)]
1385 pub authorized_voters: CliAuthorizedVoters,
1386 pub authorized_withdrawer: String,
1387 pub credits: u64,
1388 pub commission: u8,
1389 pub root_slot: Option<Slot>,
1390 pub recent_timestamp: BlockTimestamp,
1391 pub votes: Vec<CliLockout>,
1392 pub epoch_voting_history: Vec<CliEpochVotingHistory>,
1393 #[serde(skip_serializing)]
1394 pub use_lamports_unit: bool,
1395 #[serde(skip_serializing_if = "Option::is_none")]
1396 pub epoch_rewards: Option<Vec<CliEpochReward>>,
1397}
1398
1399impl QuietDisplay for CliVoteAccount {}
1400impl VerboseDisplay for CliVoteAccount {}
1401
1402impl fmt::Display for CliVoteAccount {
1403 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1404 writeln!(
1405 f,
1406 "Account Balance: {}",
1407 build_balance_message(self.account_balance, self.use_lamports_unit, true)
1408 )?;
1409 writeln!(f, "Validator Identity: {}", self.validator_identity)?;
1410 writeln!(f, "Vote Authority: {}", self.authorized_voters)?;
1411 writeln!(f, "Withdraw Authority: {}", self.authorized_withdrawer)?;
1412 writeln!(f, "Credits: {}", self.credits)?;
1413 writeln!(f, "Commission: {}%", self.commission)?;
1414 writeln!(
1415 f,
1416 "Root Slot: {}",
1417 match self.root_slot {
1418 Some(slot) => slot.to_string(),
1419 None => "~".to_string(),
1420 }
1421 )?;
1422 writeln!(
1423 f,
1424 "Recent Timestamp: {} from slot {}",
1425 unix_timestamp_to_string(self.recent_timestamp.timestamp),
1426 self.recent_timestamp.slot
1427 )?;
1428 show_votes_and_credits(f, &self.votes, &self.epoch_voting_history)?;
1429 show_epoch_rewards(f, &self.epoch_rewards)?;
1430 Ok(())
1431 }
1432}
1433
1434#[derive(Debug, Serialize, Deserialize)]
1435#[serde(rename_all = "camelCase")]
1436pub struct CliAuthorizedVoters {
1437 authorized_voters: BTreeMap<Epoch, String>,
1438}
1439
1440impl QuietDisplay for CliAuthorizedVoters {}
1441impl VerboseDisplay for CliAuthorizedVoters {}
1442
1443impl fmt::Display for CliAuthorizedVoters {
1444 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1445 write!(f, "{:?}", self.authorized_voters)
1446 }
1447}
1448
1449impl From<&AuthorizedVoters> for CliAuthorizedVoters {
1450 fn from(authorized_voters: &AuthorizedVoters) -> Self {
1451 let mut voter_map: BTreeMap<Epoch, String> = BTreeMap::new();
1452 for (epoch, voter) in authorized_voters.iter() {
1453 voter_map.insert(*epoch, voter.to_string());
1454 }
1455 Self {
1456 authorized_voters: voter_map,
1457 }
1458 }
1459}
1460
1461#[derive(Serialize, Deserialize)]
1462#[serde(rename_all = "camelCase")]
1463pub struct CliEpochVotingHistory {
1464 pub epoch: Epoch,
1465 pub slots_in_epoch: u64,
1466 pub credits_earned: u64,
1467 pub credits: u64,
1468 pub prev_credits: u64,
1469}
1470
1471#[derive(Serialize, Deserialize)]
1472#[serde(rename_all = "camelCase")]
1473pub struct CliLockout {
1474 pub slot: Slot,
1475 pub confirmation_count: u32,
1476}
1477
1478impl From<&Lockout> for CliLockout {
1479 fn from(lockout: &Lockout) -> Self {
1480 Self {
1481 slot: lockout.slot,
1482 confirmation_count: lockout.confirmation_count,
1483 }
1484 }
1485}
1486
1487#[derive(Serialize, Deserialize)]
1488#[serde(rename_all = "camelCase")]
1489pub struct CliBlockTime {
1490 pub slot: Slot,
1491 pub timestamp: UnixTimestamp,
1492}
1493
1494impl QuietDisplay for CliBlockTime {}
1495impl VerboseDisplay for CliBlockTime {}
1496
1497impl fmt::Display for CliBlockTime {
1498 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1499 writeln_name_value(f, "Block:", &self.slot.to_string())?;
1500 writeln_name_value(f, "Date:", &unix_timestamp_to_string(self.timestamp))
1501 }
1502}
1503
1504#[derive(Serialize, Deserialize)]
1505#[serde(rename_all = "camelCase")]
1506pub struct CliLeaderSchedule {
1507 pub epoch: Epoch,
1508 pub leader_schedule_entries: Vec<CliLeaderScheduleEntry>,
1509}
1510
1511impl QuietDisplay for CliLeaderSchedule {}
1512impl VerboseDisplay for CliLeaderSchedule {}
1513
1514impl fmt::Display for CliLeaderSchedule {
1515 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1516 for entry in &self.leader_schedule_entries {
1517 writeln!(f, " {:<15} {:<44}", entry.slot, entry.leader)?;
1518 }
1519 Ok(())
1520 }
1521}
1522
1523#[derive(Serialize, Deserialize)]
1524#[serde(rename_all = "camelCase")]
1525pub struct CliLeaderScheduleEntry {
1526 pub slot: Slot,
1527 pub leader: String,
1528}
1529
1530#[derive(Serialize, Deserialize)]
1531#[serde(rename_all = "camelCase")]
1532pub struct CliInflation {
1533 pub governor: RpcInflationGovernor,
1534 pub current_rate: RpcInflationRate,
1535}
1536
1537impl QuietDisplay for CliInflation {}
1538impl VerboseDisplay for CliInflation {}
1539
1540impl fmt::Display for CliInflation {
1541 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1542 writeln!(f, "{}", style("Inflation Governor:").bold())?;
1543 if (self.governor.initial - self.governor.terminal).abs() < f64::EPSILON {
1544 writeln!(
1545 f,
1546 "Fixed rate: {:>5.2}%",
1547 self.governor.terminal * 100.
1548 )?;
1549 } else {
1550 writeln!(
1551 f,
1552 "Initial rate: {:>5.2}%",
1553 self.governor.initial * 100.
1554 )?;
1555 writeln!(
1556 f,
1557 "Terminal rate: {:>5.2}%",
1558 self.governor.terminal * 100.
1559 )?;
1560 writeln!(
1561 f,
1562 "Rate reduction per year: {:>5.2}%",
1563 self.governor.taper * 100.
1564 )?;
1565 writeln!(
1566 f,
1567 "* Rate reduction is derived using the target slot time in genesis config"
1568 )?;
1569 }
1570 if self.governor.foundation_term > 0. {
1571 writeln!(
1572 f,
1573 "Foundation percentage: {:>5.2}%",
1574 self.governor.foundation
1575 )?;
1576 writeln!(
1577 f,
1578 "Foundation term: {:.1} years",
1579 self.governor.foundation_term
1580 )?;
1581 }
1582
1583 writeln!(
1584 f,
1585 "\n{}",
1586 style(format!("Inflation for Epoch {}:", self.current_rate.epoch)).bold()
1587 )?;
1588 writeln!(
1589 f,
1590 "Total rate: {:>5.2}%",
1591 self.current_rate.total * 100.
1592 )?;
1593 writeln!(
1594 f,
1595 "Staking rate: {:>5.2}%",
1596 self.current_rate.validator * 100.
1597 )?;
1598
1599 if self.current_rate.foundation > 0. {
1600 writeln!(
1601 f,
1602 "Foundation rate: {:>5.2}%",
1603 self.current_rate.foundation * 100.
1604 )?;
1605 }
1606 Ok(())
1607 }
1608}
1609
1610#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq)]
1611#[serde(rename_all = "camelCase")]
1612pub struct CliSignOnlyData {
1613 pub blockhash: String,
1614 #[serde(skip_serializing_if = "Option::is_none")]
1615 pub message: Option<String>,
1616 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1617 pub signers: Vec<String>,
1618 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1619 pub absent: Vec<String>,
1620 #[serde(skip_serializing_if = "Vec::is_empty", default)]
1621 pub bad_sig: Vec<String>,
1622}
1623
1624impl QuietDisplay for CliSignOnlyData {}
1625impl VerboseDisplay for CliSignOnlyData {}
1626
1627impl fmt::Display for CliSignOnlyData {
1628 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1629 writeln!(f)?;
1630 writeln_name_value(f, "Blockhash:", &self.blockhash)?;
1631 if let Some(message) = self.message.as_ref() {
1632 writeln_name_value(f, "Transaction Message:", message)?;
1633 }
1634 if !self.signers.is_empty() {
1635 writeln!(f, "{}", style("Signers (Pubkey=Signature):").bold())?;
1636 for signer in self.signers.iter() {
1637 writeln!(f, " {}", signer)?;
1638 }
1639 }
1640 if !self.absent.is_empty() {
1641 writeln!(f, "{}", style("Absent Signers (Pubkey):").bold())?;
1642 for pubkey in self.absent.iter() {
1643 writeln!(f, " {}", pubkey)?;
1644 }
1645 }
1646 if !self.bad_sig.is_empty() {
1647 writeln!(f, "{}", style("Bad Signatures (Pubkey):").bold())?;
1648 for pubkey in self.bad_sig.iter() {
1649 writeln!(f, " {}", pubkey)?;
1650 }
1651 }
1652 Ok(())
1653 }
1654}
1655
1656#[derive(Serialize, Deserialize)]
1657#[serde(rename_all = "camelCase")]
1658pub struct CliSignature {
1659 pub signature: String,
1660}
1661
1662impl QuietDisplay for CliSignature {}
1663impl VerboseDisplay for CliSignature {}
1664
1665impl fmt::Display for CliSignature {
1666 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1667 writeln!(f)?;
1668 writeln_name_value(f, "Signature:", &self.signature)?;
1669 Ok(())
1670 }
1671}
1672
1673#[derive(Serialize, Deserialize)]
1674#[serde(rename_all = "camelCase")]
1675pub struct CliAccountBalances {
1676 pub accounts: Vec<RpcAccountBalance>,
1677}
1678
1679impl QuietDisplay for CliAccountBalances {}
1680impl VerboseDisplay for CliAccountBalances {}
1681
1682impl fmt::Display for CliAccountBalances {
1683 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1684 writeln!(
1685 f,
1686 "{}",
1687 style(format!("{:<44} {}", "Address", "Balance")).bold()
1688 )?;
1689 for account in &self.accounts {
1690 writeln!(
1691 f,
1692 "{:<44} {}",
1693 account.address,
1694 &format!("{} SAFE", lamports_to_sol(account.lamports))
1695 )?;
1696 }
1697 Ok(())
1698 }
1699}
1700
1701#[derive(Serialize, Deserialize)]
1702#[serde(rename_all = "camelCase")]
1703pub struct CliSupply {
1704 pub total: u64,
1705 pub circulating: u64,
1706 pub non_circulating: u64,
1707 pub non_circulating_accounts: Vec<String>,
1708 #[serde(skip_serializing)]
1709 pub print_accounts: bool,
1710}
1711
1712impl From<RpcSupply> for CliSupply {
1713 fn from(rpc_supply: RpcSupply) -> Self {
1714 Self {
1715 total: rpc_supply.total,
1716 circulating: rpc_supply.circulating,
1717 non_circulating: rpc_supply.non_circulating,
1718 non_circulating_accounts: rpc_supply.non_circulating_accounts,
1719 print_accounts: false,
1720 }
1721 }
1722}
1723
1724impl QuietDisplay for CliSupply {}
1725impl VerboseDisplay for CliSupply {}
1726
1727impl fmt::Display for CliSupply {
1728 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1729 writeln_name_value(f, "Total:", &format!("{} SAFE", lamports_to_sol(self.total)))?;
1730 writeln_name_value(
1731 f,
1732 "Circulating:",
1733 &format!("{} SAFE", lamports_to_sol(self.circulating)),
1734 )?;
1735 writeln_name_value(
1736 f,
1737 "Non-Circulating:",
1738 &format!("{} SAFE", lamports_to_sol(self.non_circulating)),
1739 )?;
1740 if self.print_accounts {
1741 writeln!(f)?;
1742 writeln_name_value(f, "Non-Circulating Accounts:", " ")?;
1743 for account in &self.non_circulating_accounts {
1744 writeln!(f, " {}", account)?;
1745 }
1746 }
1747 Ok(())
1748 }
1749}
1750
1751#[derive(Serialize, Deserialize)]
1752#[serde(rename_all = "camelCase")]
1753pub struct CliFeesInner {
1754 pub slot: Slot,
1755 pub blockhash: String,
1756 pub lamports_per_signature: u64,
1757 pub last_valid_slot: Option<Slot>,
1758 pub last_valid_block_height: Option<Slot>,
1759}
1760
1761impl QuietDisplay for CliFeesInner {}
1762impl VerboseDisplay for CliFeesInner {}
1763
1764impl fmt::Display for CliFeesInner {
1765 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1766 writeln_name_value(f, "Blockhash:", &self.blockhash)?;
1767 writeln_name_value(
1768 f,
1769 "Lamports per signature:",
1770 &self.lamports_per_signature.to_string(),
1771 )?;
1772 let last_valid_block_height = self
1773 .last_valid_block_height
1774 .map(|s| s.to_string())
1775 .unwrap_or_default();
1776 writeln_name_value(f, "Last valid block height:", &last_valid_block_height)
1777 }
1778}
1779
1780#[derive(Serialize, Deserialize)]
1781#[serde(rename_all = "camelCase")]
1782pub struct CliFees {
1783 #[serde(flatten, skip_serializing_if = "Option::is_none")]
1784 pub inner: Option<CliFeesInner>,
1785}
1786
1787impl QuietDisplay for CliFees {}
1788impl VerboseDisplay for CliFees {}
1789
1790impl fmt::Display for CliFees {
1791 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1792 match self.inner.as_ref() {
1793 Some(inner) => write!(f, "{}", inner),
1794 None => write!(f, "Fees unavailable"),
1795 }
1796 }
1797}
1798
1799impl CliFees {
1800 pub fn some(
1801 slot: Slot,
1802 blockhash: Hash,
1803 lamports_per_signature: u64,
1804 last_valid_slot: Option<Slot>,
1805 last_valid_block_height: Option<Slot>,
1806 ) -> Self {
1807 Self {
1808 inner: Some(CliFeesInner {
1809 slot,
1810 blockhash: blockhash.to_string(),
1811 lamports_per_signature,
1812 last_valid_slot,
1813 last_valid_block_height,
1814 }),
1815 }
1816 }
1817 pub fn none() -> Self {
1818 Self { inner: None }
1819 }
1820}
1821
1822#[derive(Serialize, Deserialize)]
1823#[serde(rename_all = "camelCase")]
1824pub struct CliTokenAccount {
1825 pub address: String,
1826 #[serde(flatten)]
1827 pub token_account: UiTokenAccount,
1828}
1829
1830impl QuietDisplay for CliTokenAccount {}
1831impl VerboseDisplay for CliTokenAccount {}
1832
1833impl fmt::Display for CliTokenAccount {
1834 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1835 writeln!(f)?;
1836 writeln_name_value(f, "Address:", &self.address)?;
1837 let account = &self.token_account;
1838 writeln_name_value(
1839 f,
1840 "Balance:",
1841 &account.token_amount.real_number_string_trimmed(),
1842 )?;
1843 let mint = format!(
1844 "{}{}",
1845 account.mint,
1846 if account.is_native { " (native)" } else { "" }
1847 );
1848 writeln_name_value(f, "Mint:", &mint)?;
1849 writeln_name_value(f, "Owner:", &account.owner)?;
1850 writeln_name_value(f, "State:", &format!("{:?}", account.state))?;
1851 if let Some(delegate) = &account.delegate {
1852 writeln!(f, "Delegation:")?;
1853 writeln_name_value(f, " Delegate:", delegate)?;
1854 let allowance = account.delegated_amount.as_ref().unwrap();
1855 writeln_name_value(f, " Allowance:", &allowance.real_number_string_trimmed())?;
1856 }
1857 writeln_name_value(
1858 f,
1859 "Close authority:",
1860 account.close_authority.as_ref().unwrap_or(&String::new()),
1861 )?;
1862 Ok(())
1863 }
1864}
1865
1866#[derive(Serialize, Deserialize)]
1867#[serde(rename_all = "camelCase")]
1868pub struct CliProgramId {
1869 pub program_id: String,
1870}
1871
1872impl QuietDisplay for CliProgramId {}
1873impl VerboseDisplay for CliProgramId {}
1874
1875impl fmt::Display for CliProgramId {
1876 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1877 writeln_name_value(f, "Program Id:", &self.program_id)
1878 }
1879}
1880
1881#[derive(Serialize, Deserialize)]
1882#[serde(rename_all = "camelCase")]
1883pub struct CliProgramBuffer {
1884 pub buffer: String,
1885}
1886
1887impl QuietDisplay for CliProgramBuffer {}
1888impl VerboseDisplay for CliProgramBuffer {}
1889
1890impl fmt::Display for CliProgramBuffer {
1891 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1892 writeln_name_value(f, "Buffer:", &self.buffer)
1893 }
1894}
1895
1896#[derive(Debug, Serialize, Deserialize)]
1897#[serde(rename_all = "camelCase")]
1898pub enum CliProgramAccountType {
1899 Buffer,
1900 Program,
1901}
1902
1903#[derive(Serialize, Deserialize)]
1904#[serde(rename_all = "camelCase")]
1905pub struct CliProgramAuthority {
1906 pub authority: String,
1907 pub account_type: CliProgramAccountType,
1908}
1909
1910impl QuietDisplay for CliProgramAuthority {}
1911impl VerboseDisplay for CliProgramAuthority {}
1912
1913impl fmt::Display for CliProgramAuthority {
1914 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1915 writeln_name_value(f, "Account Type:", &format!("{:?}", self.account_type))?;
1916 writeln_name_value(f, "Authority:", &self.authority)
1917 }
1918}
1919
1920#[derive(Serialize, Deserialize)]
1921#[serde(rename_all = "camelCase")]
1922pub struct CliProgram {
1923 pub program_id: String,
1924 pub owner: String,
1925 pub data_len: usize,
1926}
1927impl QuietDisplay for CliProgram {}
1928impl VerboseDisplay for CliProgram {}
1929impl fmt::Display for CliProgram {
1930 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1931 writeln!(f)?;
1932 writeln_name_value(f, "Program Id:", &self.program_id)?;
1933 writeln_name_value(f, "Owner:", &self.owner)?;
1934 writeln_name_value(
1935 f,
1936 "Data Length:",
1937 &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
1938 )?;
1939 Ok(())
1940 }
1941}
1942
1943#[derive(Serialize, Deserialize)]
1944#[serde(rename_all = "camelCase")]
1945pub struct CliUpgradeableProgram {
1946 pub program_id: String,
1947 pub owner: String,
1948 pub programdata_address: String,
1949 pub authority: String,
1950 pub last_deploy_slot: u64,
1951 pub data_len: usize,
1952 pub lamports: u64,
1953 #[serde(skip_serializing)]
1954 pub use_lamports_unit: bool,
1955}
1956impl QuietDisplay for CliUpgradeableProgram {}
1957impl VerboseDisplay for CliUpgradeableProgram {}
1958impl fmt::Display for CliUpgradeableProgram {
1959 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1960 writeln!(f)?;
1961 writeln_name_value(f, "Program Id:", &self.program_id)?;
1962 writeln_name_value(f, "Owner:", &self.owner)?;
1963 writeln_name_value(f, "ProgramData Address:", &self.programdata_address)?;
1964 writeln_name_value(f, "Authority:", &self.authority)?;
1965 writeln_name_value(
1966 f,
1967 "Last Deployed In Slot:",
1968 &self.last_deploy_slot.to_string(),
1969 )?;
1970 writeln_name_value(
1971 f,
1972 "Data Length:",
1973 &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
1974 )?;
1975 writeln_name_value(
1976 f,
1977 "Balance:",
1978 &build_balance_message(self.lamports, self.use_lamports_unit, true),
1979 )?;
1980 Ok(())
1981 }
1982}
1983
1984#[derive(Serialize, Deserialize)]
1985#[serde(rename_all = "camelCase")]
1986pub struct CliUpgradeablePrograms {
1987 pub programs: Vec<CliUpgradeableProgram>,
1988 #[serde(skip_serializing)]
1989 pub use_lamports_unit: bool,
1990}
1991impl QuietDisplay for CliUpgradeablePrograms {}
1992impl VerboseDisplay for CliUpgradeablePrograms {}
1993impl fmt::Display for CliUpgradeablePrograms {
1994 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1995 writeln!(f)?;
1996 writeln!(
1997 f,
1998 "{}",
1999 style(format!(
2000 "{:<44} | {:<9} | {:<44} | {}",
2001 "Program Id", "Slot", "Authority", "Balance"
2002 ))
2003 .bold()
2004 )?;
2005 for program in self.programs.iter() {
2006 writeln!(
2007 f,
2008 "{}",
2009 &format!(
2010 "{:<44} | {:<9} | {:<44} | {}",
2011 program.program_id,
2012 program.last_deploy_slot,
2013 program.authority,
2014 build_balance_message(program.lamports, self.use_lamports_unit, true)
2015 )
2016 )?;
2017 }
2018 Ok(())
2019 }
2020}
2021
2022#[derive(Serialize, Deserialize)]
2023#[serde(rename_all = "camelCase")]
2024pub struct CliUpgradeableProgramClosed {
2025 pub program_id: String,
2026 pub lamports: u64,
2027 #[serde(skip_serializing)]
2028 pub use_lamports_unit: bool,
2029}
2030impl QuietDisplay for CliUpgradeableProgramClosed {}
2031impl VerboseDisplay for CliUpgradeableProgramClosed {}
2032impl fmt::Display for CliUpgradeableProgramClosed {
2033 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2034 writeln!(f)?;
2035 writeln!(
2036 f,
2037 "Closed Program Id {}, {} reclaimed",
2038 &self.program_id,
2039 &build_balance_message(self.lamports, self.use_lamports_unit, true)
2040 )?;
2041 Ok(())
2042 }
2043}
2044
2045#[derive(Clone, Serialize, Deserialize)]
2046#[serde(rename_all = "camelCase")]
2047pub struct CliUpgradeableBuffer {
2048 pub address: String,
2049 pub authority: String,
2050 pub data_len: usize,
2051 pub lamports: u64,
2052 #[serde(skip_serializing)]
2053 pub use_lamports_unit: bool,
2054}
2055impl QuietDisplay for CliUpgradeableBuffer {}
2056impl VerboseDisplay for CliUpgradeableBuffer {}
2057impl fmt::Display for CliUpgradeableBuffer {
2058 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2059 writeln!(f)?;
2060 writeln_name_value(f, "Buffer Address:", &self.address)?;
2061 writeln_name_value(f, "Authority:", &self.authority)?;
2062 writeln_name_value(
2063 f,
2064 "Balance:",
2065 &build_balance_message(self.lamports, self.use_lamports_unit, true),
2066 )?;
2067 writeln_name_value(
2068 f,
2069 "Data Length:",
2070 &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len),
2071 )?;
2072
2073 Ok(())
2074 }
2075}
2076
2077#[derive(Serialize, Deserialize)]
2078#[serde(rename_all = "camelCase")]
2079pub struct CliUpgradeableBuffers {
2080 pub buffers: Vec<CliUpgradeableBuffer>,
2081 #[serde(skip_serializing)]
2082 pub use_lamports_unit: bool,
2083}
2084impl QuietDisplay for CliUpgradeableBuffers {}
2085impl VerboseDisplay for CliUpgradeableBuffers {}
2086impl fmt::Display for CliUpgradeableBuffers {
2087 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2088 writeln!(f)?;
2089 writeln!(
2090 f,
2091 "{}",
2092 style(format!(
2093 "{:<44} | {:<44} | {}",
2094 "Buffer Address", "Authority", "Balance"
2095 ))
2096 .bold()
2097 )?;
2098 for buffer in self.buffers.iter() {
2099 writeln!(
2100 f,
2101 "{}",
2102 &format!(
2103 "{:<44} | {:<44} | {}",
2104 buffer.address,
2105 buffer.authority,
2106 build_balance_message(buffer.lamports, self.use_lamports_unit, true)
2107 )
2108 )?;
2109 }
2110 Ok(())
2111 }
2112}
2113
2114#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
2115#[serde(rename_all = "camelCase")]
2116pub struct CliAddressLookupTable {
2117 pub lookup_table_address: String,
2118 pub authority: Option<String>,
2119 pub deactivation_slot: u64,
2120 pub last_extended_slot: u64,
2121 pub addresses: Vec<String>,
2122}
2123impl QuietDisplay for CliAddressLookupTable {}
2124impl VerboseDisplay for CliAddressLookupTable {}
2125impl fmt::Display for CliAddressLookupTable {
2126 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2127 writeln!(f)?;
2128 writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
2129 if let Some(authority) = &self.authority {
2130 writeln_name_value(f, "Authority:", authority)?;
2131 } else {
2132 writeln_name_value(f, "Authority:", "None (frozen)")?;
2133 }
2134 if self.deactivation_slot == u64::MAX {
2135 writeln_name_value(f, "Deactivation Slot:", "None (still active)")?;
2136 } else {
2137 writeln_name_value(f, "Deactivation Slot:", &self.deactivation_slot.to_string())?;
2138 }
2139 if self.last_extended_slot == 0 {
2140 writeln_name_value(f, "Last Extended Slot:", "None (empty)")?;
2141 } else {
2142 writeln_name_value(
2143 f,
2144 "Last Extended Slot:",
2145 &self.last_extended_slot.to_string(),
2146 )?;
2147 }
2148 if self.addresses.is_empty() {
2149 writeln_name_value(f, "Address Table Entries:", "None (empty)")?;
2150 } else {
2151 writeln!(f, "{}", style("Address Table Entries:".to_string()).bold())?;
2152 writeln!(f)?;
2153 writeln!(
2154 f,
2155 "{}",
2156 style(format!(" {:<5} {}", "Index", "Address")).bold()
2157 )?;
2158 for (index, address) in self.addresses.iter().enumerate() {
2159 writeln!(f, " {:<5} {}", index, address)?;
2160 }
2161 }
2162 Ok(())
2163 }
2164}
2165
2166#[derive(Serialize, Deserialize)]
2167#[serde(rename_all = "camelCase")]
2168pub struct CliAddressLookupTableCreated {
2169 pub lookup_table_address: String,
2170 pub signature: String,
2171}
2172impl QuietDisplay for CliAddressLookupTableCreated {}
2173impl VerboseDisplay for CliAddressLookupTableCreated {}
2174impl fmt::Display for CliAddressLookupTableCreated {
2175 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2176 writeln!(f)?;
2177 writeln_name_value(f, "Signature:", &self.signature)?;
2178 writeln_name_value(f, "Lookup Table Address:", &self.lookup_table_address)?;
2179 Ok(())
2180 }
2181}
2182
2183#[derive(Debug, Default)]
2184pub struct ReturnSignersConfig {
2185 pub dump_transaction_message: bool,
2186}
2187
2188pub fn return_signers(
2189 tx: &Transaction,
2190 output_format: &OutputFormat,
2191) -> Result<String, Box<dyn std::error::Error>> {
2192 return_signers_with_config(tx, output_format, &ReturnSignersConfig::default())
2193}
2194
2195pub fn return_signers_with_config(
2196 tx: &Transaction,
2197 output_format: &OutputFormat,
2198 config: &ReturnSignersConfig,
2199) -> Result<String, Box<dyn std::error::Error>> {
2200 let cli_command = return_signers_data(tx, config);
2201 Ok(output_format.formatted_string(&cli_command))
2202}
2203
2204pub fn return_signers_data(tx: &Transaction, config: &ReturnSignersConfig) -> CliSignOnlyData {
2205 let verify_results = tx.verify_with_results();
2206 let mut signers = Vec::new();
2207 let mut absent = Vec::new();
2208 let mut bad_sig = Vec::new();
2209 tx.signatures
2210 .iter()
2211 .zip(tx.message.account_keys.iter())
2212 .zip(verify_results.into_iter())
2213 .for_each(|((sig, key), res)| {
2214 if res {
2215 signers.push(format!("{}={}", key, sig))
2216 } else if *sig == Signature::default() {
2217 absent.push(key.to_string());
2218 } else {
2219 bad_sig.push(key.to_string());
2220 }
2221 });
2222 let message = if config.dump_transaction_message {
2223 let message_data = tx.message_data();
2224 Some(base64::encode(&message_data))
2225 } else {
2226 None
2227 };
2228
2229 CliSignOnlyData {
2230 blockhash: tx.message.recent_blockhash.to_string(),
2231 message,
2232 signers,
2233 absent,
2234 bad_sig,
2235 }
2236}
2237
2238pub fn parse_sign_only_reply_string(reply: &str) -> SignOnly {
2239 let object: Value = serde_json::from_str(reply).unwrap();
2240 let blockhash_str = object.get("blockhash").unwrap().as_str().unwrap();
2241 let blockhash = blockhash_str.parse::<Hash>().unwrap();
2242 let mut present_signers: Vec<(Pubkey, Signature)> = Vec::new();
2243 let signer_strings = object.get("signers");
2244 if let Some(sig_strings) = signer_strings {
2245 present_signers = sig_strings
2246 .as_array()
2247 .unwrap()
2248 .iter()
2249 .map(|signer_string| {
2250 let mut signer = signer_string.as_str().unwrap().split('=');
2251 let key = Pubkey::from_str(signer.next().unwrap()).unwrap();
2252 let sig = Signature::from_str(signer.next().unwrap()).unwrap();
2253 (key, sig)
2254 })
2255 .collect();
2256 }
2257 let mut absent_signers: Vec<Pubkey> = Vec::new();
2258 let signer_strings = object.get("absent");
2259 if let Some(sig_strings) = signer_strings {
2260 absent_signers = sig_strings
2261 .as_array()
2262 .unwrap()
2263 .iter()
2264 .map(|val| {
2265 let s = val.as_str().unwrap();
2266 Pubkey::from_str(s).unwrap()
2267 })
2268 .collect();
2269 }
2270 let mut bad_signers: Vec<Pubkey> = Vec::new();
2271 let signer_strings = object.get("badSig");
2272 if let Some(sig_strings) = signer_strings {
2273 bad_signers = sig_strings
2274 .as_array()
2275 .unwrap()
2276 .iter()
2277 .map(|val| {
2278 let s = val.as_str().unwrap();
2279 Pubkey::from_str(s).unwrap()
2280 })
2281 .collect();
2282 }
2283
2284 let message = object
2285 .get("message")
2286 .and_then(|o| o.as_str())
2287 .map(|m| m.to_string());
2288
2289 SignOnly {
2290 blockhash,
2291 message,
2292 present_signers,
2293 absent_signers,
2294 bad_signers,
2295 }
2296}
2297
2298#[derive(Debug, Serialize, Deserialize)]
2299#[serde(rename_all = "camelCase")]
2300pub enum CliSignatureVerificationStatus {
2301 None,
2302 Pass,
2303 Fail,
2304}
2305
2306impl CliSignatureVerificationStatus {
2307 pub fn verify_transaction(tx: &VersionedTransaction) -> Vec<Self> {
2308 tx.verify_with_results()
2309 .iter()
2310 .zip(&tx.signatures)
2311 .map(|(stat, sig)| match stat {
2312 true => CliSignatureVerificationStatus::Pass,
2313 false if sig == &Signature::default() => CliSignatureVerificationStatus::None,
2314 false => CliSignatureVerificationStatus::Fail,
2315 })
2316 .collect()
2317 }
2318}
2319
2320impl fmt::Display for CliSignatureVerificationStatus {
2321 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2322 match self {
2323 Self::None => write!(f, "none"),
2324 Self::Pass => write!(f, "pass"),
2325 Self::Fail => write!(f, "fail"),
2326 }
2327 }
2328}
2329
2330#[derive(Serialize, Deserialize)]
2331#[serde(rename_all = "camelCase")]
2332pub struct CliBlock {
2333 #[serde(flatten)]
2334 pub encoded_confirmed_block: EncodedConfirmedBlock,
2335 #[serde(skip_serializing)]
2336 pub slot: Slot,
2337}
2338
2339impl QuietDisplay for CliBlock {}
2340impl VerboseDisplay for CliBlock {}
2341
2342impl fmt::Display for CliBlock {
2343 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2344 writeln!(f, "Slot: {}", self.slot)?;
2345 writeln!(
2346 f,
2347 "Parent Slot: {}",
2348 self.encoded_confirmed_block.parent_slot
2349 )?;
2350 writeln!(f, "Blockhash: {}", self.encoded_confirmed_block.blockhash)?;
2351 writeln!(
2352 f,
2353 "Previous Blockhash: {}",
2354 self.encoded_confirmed_block.previous_blockhash
2355 )?;
2356 if let Some(block_time) = self.encoded_confirmed_block.block_time {
2357 writeln!(f, "Block Time: {:?}", Local.timestamp(block_time, 0))?;
2358 }
2359 if let Some(block_height) = self.encoded_confirmed_block.block_height {
2360 writeln!(f, "Block Height: {:?}", block_height)?;
2361 }
2362 if !self.encoded_confirmed_block.rewards.is_empty() {
2363 let mut rewards = self.encoded_confirmed_block.rewards.clone();
2364 rewards.sort_by(|a, b| a.pubkey.cmp(&b.pubkey));
2365 let mut total_rewards = 0;
2366 writeln!(f, "Rewards:")?;
2367 writeln!(
2368 f,
2369 " {:<44} {:^15} {:<15} {:<20} {:>14} {:>10}",
2370 "Address", "Type", "Amount", "New Balance", "Percent Change", "Commission"
2371 )?;
2372 for reward in rewards {
2373 let sign = if reward.lamports < 0 { "-" } else { "" };
2374
2375 total_rewards += reward.lamports;
2376 #[allow(clippy::format_in_format_args)]
2377 writeln!(
2378 f,
2379 " {:<44} {:^15} {:>15} {} {}",
2380 reward.pubkey,
2381 if let Some(reward_type) = reward.reward_type {
2382 format!("{}", reward_type)
2383 } else {
2384 "-".to_string()
2385 },
2386 format!(
2387 "{}◎{:<14.9}",
2388 sign,
2389 lamports_to_sol(reward.lamports.unsigned_abs())
2390 ),
2391 if reward.post_balance == 0 {
2392 " - -".to_string()
2393 } else {
2394 format!(
2395 "◎{:<19.9} {:>13.9}%",
2396 lamports_to_sol(reward.post_balance),
2397 (reward.lamports.abs() as f64
2398 / (reward.post_balance as f64 - reward.lamports as f64))
2399 * 100.0
2400 )
2401 },
2402 reward
2403 .commission
2404 .map(|commission| format!("{:>9}%", commission))
2405 .unwrap_or_else(|| " -".to_string())
2406 )?;
2407 }
2408
2409 let sign = if total_rewards < 0 { "-" } else { "" };
2410 writeln!(
2411 f,
2412 "Total Rewards: {}◎{:<12.9}",
2413 sign,
2414 lamports_to_sol(total_rewards.unsigned_abs())
2415 )?;
2416 }
2417 for (index, transaction_with_meta) in
2418 self.encoded_confirmed_block.transactions.iter().enumerate()
2419 {
2420 writeln!(f, "Transaction {}:", index)?;
2421 writeln_transaction(
2422 f,
2423 &transaction_with_meta.transaction.decode().unwrap(),
2424 transaction_with_meta.meta.as_ref(),
2425 " ",
2426 None,
2427 None,
2428 )?;
2429 }
2430 Ok(())
2431 }
2432}
2433
2434#[derive(Serialize, Deserialize)]
2435#[serde(rename_all = "camelCase")]
2436pub struct CliTransaction {
2437 pub transaction: EncodedTransaction,
2438 pub meta: Option<UiTransactionStatusMeta>,
2439 pub block_time: Option<UnixTimestamp>,
2440 #[serde(skip_serializing)]
2441 pub slot: Option<Slot>,
2442 #[serde(skip_serializing)]
2443 pub decoded_transaction: VersionedTransaction,
2444 #[serde(skip_serializing)]
2445 pub prefix: String,
2446 #[serde(skip_serializing_if = "Vec::is_empty")]
2447 pub sigverify_status: Vec<CliSignatureVerificationStatus>,
2448}
2449
2450impl QuietDisplay for CliTransaction {}
2451impl VerboseDisplay for CliTransaction {}
2452
2453impl fmt::Display for CliTransaction {
2454 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2455 writeln_transaction(
2456 f,
2457 &self.decoded_transaction,
2458 self.meta.as_ref(),
2459 &self.prefix,
2460 if !self.sigverify_status.is_empty() {
2461 Some(&self.sigverify_status)
2462 } else {
2463 None
2464 },
2465 self.block_time,
2466 )
2467 }
2468}
2469
2470#[derive(Serialize, Deserialize)]
2471#[serde(rename_all = "camelCase")]
2472pub struct CliTransactionConfirmation {
2473 pub confirmation_status: Option<TransactionConfirmationStatus>,
2474 #[serde(flatten, skip_serializing_if = "Option::is_none")]
2475 pub transaction: Option<CliTransaction>,
2476 #[serde(skip_serializing)]
2477 pub get_transaction_error: Option<String>,
2478 #[serde(skip_serializing_if = "Option::is_none")]
2479 pub err: Option<TransactionError>,
2480}
2481
2482impl QuietDisplay for CliTransactionConfirmation {}
2483impl VerboseDisplay for CliTransactionConfirmation {
2484 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
2485 if let Some(transaction) = &self.transaction {
2486 writeln!(
2487 w,
2488 "\nTransaction executed in slot {}:",
2489 transaction.slot.expect("slot should exist")
2490 )?;
2491 write!(w, "{}", transaction)?;
2492 } else if let Some(confirmation_status) = &self.confirmation_status {
2493 if confirmation_status != &TransactionConfirmationStatus::Finalized {
2494 writeln!(w)?;
2495 writeln!(
2496 w,
2497 "Unable to get finalized transaction details: not yet finalized"
2498 )?;
2499 } else if let Some(err) = &self.get_transaction_error {
2500 writeln!(w)?;
2501 writeln!(w, "Unable to get finalized transaction details: {}", err)?;
2502 }
2503 }
2504 writeln!(w)?;
2505 write!(w, "{}", self)
2506 }
2507}
2508
2509impl fmt::Display for CliTransactionConfirmation {
2510 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2511 match &self.confirmation_status {
2512 None => write!(f, "Not found"),
2513 Some(confirmation_status) => {
2514 if let Some(err) = &self.err {
2515 write!(f, "Transaction failed: {}", err)
2516 } else {
2517 write!(f, "{:?}", confirmation_status)
2518 }
2519 }
2520 }
2521 }
2522}
2523
2524#[derive(Serialize, Deserialize)]
2525#[serde(rename_all = "camelCase")]
2526pub struct CliGossipNode {
2527 #[serde(skip_serializing_if = "Option::is_none")]
2528 pub ip_address: Option<String>,
2529 #[serde(skip_serializing_if = "Option::is_none")]
2530 pub identity_label: Option<String>,
2531 pub identity_pubkey: String,
2532 #[serde(skip_serializing_if = "Option::is_none")]
2533 pub gossip_port: Option<u16>,
2534 #[serde(skip_serializing_if = "Option::is_none")]
2535 pub tpu_port: Option<u16>,
2536 #[serde(skip_serializing_if = "Option::is_none")]
2537 pub rpc_host: Option<String>,
2538 #[serde(skip_serializing_if = "Option::is_none")]
2539 pub version: Option<String>,
2540 #[serde(skip_serializing_if = "Option::is_none")]
2541 pub feature_set: Option<u32>,
2542}
2543
2544impl CliGossipNode {
2545 pub fn new(info: RpcContactInfo, labels: &HashMap<String, String>) -> Self {
2546 Self {
2547 ip_address: info.gossip.map(|addr| addr.ip().to_string()),
2548 identity_label: labels.get(&info.pubkey).cloned(),
2549 identity_pubkey: info.pubkey,
2550 gossip_port: info.gossip.map(|addr| addr.port()),
2551 tpu_port: info.tpu.map(|addr| addr.port()),
2552 rpc_host: info.rpc.map(|addr| addr.to_string()),
2553 version: info.version,
2554 feature_set: info.feature_set,
2555 }
2556 }
2557}
2558
2559fn unwrap_to_string_or_none<T>(option: Option<T>) -> String
2560where
2561 T: std::string::ToString,
2562{
2563 unwrap_to_string_or_default(option, "none")
2564}
2565
2566fn unwrap_to_string_or_default<T>(option: Option<T>, default: &str) -> String
2567where
2568 T: std::string::ToString,
2569{
2570 option
2571 .as_ref()
2572 .map(|v| v.to_string())
2573 .unwrap_or_else(|| default.to_string())
2574}
2575
2576impl fmt::Display for CliGossipNode {
2577 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2578 write!(
2579 f,
2580 "{:15} | {:44} | {:6} | {:5} | {:21} | {:8}| {}",
2581 unwrap_to_string_or_none(self.ip_address.as_ref()),
2582 self.identity_label
2583 .as_ref()
2584 .unwrap_or(&self.identity_pubkey),
2585 unwrap_to_string_or_none(self.gossip_port.as_ref()),
2586 unwrap_to_string_or_none(self.tpu_port.as_ref()),
2587 unwrap_to_string_or_none(self.rpc_host.as_ref()),
2588 unwrap_to_string_or_default(self.version.as_ref(), "unknown"),
2589 unwrap_to_string_or_default(self.feature_set.as_ref(), "unknown"),
2590 )
2591 }
2592}
2593
2594impl QuietDisplay for CliGossipNode {}
2595impl VerboseDisplay for CliGossipNode {}
2596
2597#[derive(Serialize, Deserialize)]
2598pub struct CliGossipNodes(pub Vec<CliGossipNode>);
2599
2600impl fmt::Display for CliGossipNodes {
2601 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2602 writeln!(
2603 f,
2604 "IP Address | Identity \
2605 | Gossip | TPU | RPC Address | Version | Feature Set\n\
2606 ----------------+----------------------------------------------+\
2607 --------+-------+-----------------------+---------+----------------",
2608 )?;
2609 for node in self.0.iter() {
2610 writeln!(f, "{}", node)?;
2611 }
2612 writeln!(f, "Nodes: {}", self.0.len())
2613 }
2614}
2615
2616impl QuietDisplay for CliGossipNodes {}
2617impl VerboseDisplay for CliGossipNodes {}
2618
2619#[derive(Serialize, Deserialize)]
2620#[serde(rename_all = "camelCase")]
2621pub struct CliPing {
2622 pub source_pubkey: String,
2623 #[serde(skip_serializing_if = "Option::is_none")]
2624 pub fixed_blockhash: Option<String>,
2625 #[serde(skip_serializing)]
2626 pub blockhash_from_cluster: bool,
2627 pub pings: Vec<CliPingData>,
2628 pub transaction_stats: CliPingTxStats,
2629 #[serde(skip_serializing_if = "Option::is_none")]
2630 pub confirmation_stats: Option<CliPingConfirmationStats>,
2631}
2632
2633impl fmt::Display for CliPing {
2634 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2635 writeln!(f)?;
2636 writeln_name_value(f, "Source Account:", &self.source_pubkey)?;
2637 if let Some(fixed_blockhash) = &self.fixed_blockhash {
2638 let blockhash_origin = if self.blockhash_from_cluster {
2639 "fetched from cluster"
2640 } else {
2641 "supplied from cli arguments"
2642 };
2643 writeln!(
2644 f,
2645 "Fixed blockhash is used: {} ({})",
2646 fixed_blockhash, blockhash_origin
2647 )?;
2648 }
2649 writeln!(f)?;
2650 for ping in &self.pings {
2651 write!(f, "{}", ping)?;
2652 }
2653 writeln!(f)?;
2654 writeln!(f, "--- transaction statistics ---")?;
2655 write!(f, "{}", self.transaction_stats)?;
2656 if let Some(confirmation_stats) = &self.confirmation_stats {
2657 write!(f, "{}", confirmation_stats)?;
2658 }
2659 Ok(())
2660 }
2661}
2662
2663impl QuietDisplay for CliPing {}
2664impl VerboseDisplay for CliPing {}
2665
2666#[derive(Serialize, Deserialize)]
2667#[serde(rename_all = "camelCase")]
2668pub struct CliPingData {
2669 pub success: bool,
2670 #[serde(skip_serializing_if = "Option::is_none")]
2671 pub signature: Option<String>,
2672 #[serde(skip_serializing_if = "Option::is_none")]
2673 pub ms: Option<u64>,
2674 #[serde(skip_serializing_if = "Option::is_none")]
2675 pub error: Option<String>,
2676 #[serde(skip_serializing)]
2677 pub print_timestamp: bool,
2678 pub timestamp: String,
2679 pub sequence: u64,
2680 #[serde(skip_serializing_if = "Option::is_none")]
2681 pub lamports: Option<u64>,
2682}
2683impl fmt::Display for CliPingData {
2684 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2685 let (mark, msg) = if let Some(signature) = &self.signature {
2686 if self.success {
2687 (
2688 CHECK_MARK,
2689 format!(
2690 "{} lamport(s) transferred: seq={:<3} time={:>4}ms signature={}",
2691 self.lamports.unwrap(),
2692 self.sequence,
2693 self.ms.unwrap(),
2694 signature
2695 ),
2696 )
2697 } else if let Some(error) = &self.error {
2698 (
2699 CROSS_MARK,
2700 format!(
2701 "Transaction failed: seq={:<3} error={:?} signature={}",
2702 self.sequence, error, signature
2703 ),
2704 )
2705 } else {
2706 (
2707 CROSS_MARK,
2708 format!(
2709 "Confirmation timeout: seq={:<3} signature={}",
2710 self.sequence, signature
2711 ),
2712 )
2713 }
2714 } else {
2715 (
2716 CROSS_MARK,
2717 format!(
2718 "Submit failed: seq={:<3} error={:?}",
2719 self.sequence,
2720 self.error.as_ref().unwrap(),
2721 ),
2722 )
2723 };
2724
2725 writeln!(
2726 f,
2727 "{}{}{}",
2728 if self.print_timestamp {
2729 &self.timestamp
2730 } else {
2731 ""
2732 },
2733 mark,
2734 msg
2735 )
2736 }
2737}
2738
2739impl QuietDisplay for CliPingData {}
2740impl VerboseDisplay for CliPingData {}
2741
2742#[derive(Serialize, Deserialize)]
2743#[serde(rename_all = "camelCase")]
2744pub struct CliPingTxStats {
2745 pub num_transactions: u32,
2746 pub num_transaction_confirmed: u32,
2747}
2748impl fmt::Display for CliPingTxStats {
2749 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2750 writeln!(
2751 f,
2752 "{} transactions submitted, {} transactions confirmed, {:.1}% transaction loss",
2753 self.num_transactions,
2754 self.num_transaction_confirmed,
2755 (100.
2756 - f64::from(self.num_transaction_confirmed) / f64::from(self.num_transactions)
2757 * 100.)
2758 )
2759 }
2760}
2761
2762impl QuietDisplay for CliPingTxStats {}
2763impl VerboseDisplay for CliPingTxStats {}
2764
2765#[derive(Serialize, Deserialize)]
2766#[serde(rename_all = "camelCase")]
2767pub struct CliPingConfirmationStats {
2768 pub min: f64,
2769 pub mean: f64,
2770 pub max: f64,
2771 pub std_dev: f64,
2772}
2773impl fmt::Display for CliPingConfirmationStats {
2774 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2775 writeln!(
2776 f,
2777 "confirmation min/mean/max/stddev = {:.0}/{:.0}/{:.0}/{:.0} ms",
2778 self.min, self.mean, self.max, self.std_dev,
2779 )
2780 }
2781}
2782impl QuietDisplay for CliPingConfirmationStats {}
2783impl VerboseDisplay for CliPingConfirmationStats {}
2784
2785#[derive(Serialize, Deserialize, Debug)]
2786#[serde(rename_all = "camelCase")]
2787pub struct CliBalance {
2788 pub lamports: u64,
2789 #[serde(skip)]
2790 pub config: BuildBalanceMessageConfig,
2791}
2792
2793impl QuietDisplay for CliBalance {
2794 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
2795 let config = BuildBalanceMessageConfig {
2796 show_unit: false,
2797 trim_trailing_zeros: true,
2798 ..self.config
2799 };
2800 let balance_message = build_balance_message_with_config(self.lamports, &config);
2801 write!(w, "{}", balance_message)
2802 }
2803}
2804
2805impl VerboseDisplay for CliBalance {
2806 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
2807 let config = BuildBalanceMessageConfig {
2808 show_unit: true,
2809 trim_trailing_zeros: false,
2810 ..self.config
2811 };
2812 let balance_message = build_balance_message_with_config(self.lamports, &config);
2813 write!(w, "{}", balance_message)
2814 }
2815}
2816
2817impl fmt::Display for CliBalance {
2818 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2819 let balance_message = build_balance_message_with_config(self.lamports, &self.config);
2820 write!(f, "{}", balance_message)
2821 }
2822}
2823
2824#[cfg(test)]
2825mod tests {
2826 use {
2827 super::*,
2828 clap::{App, Arg},
2829 solana_sdk::{
2830 message::Message,
2831 pubkey::Pubkey,
2832 signature::{keypair_from_seed, NullSigner, Signature, Signer, SignerError},
2833 system_instruction,
2834 transaction::Transaction,
2835 },
2836 };
2837
2838 #[test]
2839 fn test_return_signers() {
2840 struct BadSigner {
2841 pubkey: Pubkey,
2842 }
2843
2844 impl BadSigner {
2845 pub fn new(pubkey: Pubkey) -> Self {
2846 Self { pubkey }
2847 }
2848 }
2849
2850 impl Signer for BadSigner {
2851 fn try_pubkey(&self) -> Result<Pubkey, SignerError> {
2852 Ok(self.pubkey)
2853 }
2854
2855 fn try_sign_message(&self, _message: &[u8]) -> Result<Signature, SignerError> {
2856 Ok(Signature::new(&[1u8; 64]))
2857 }
2858
2859 fn is_interactive(&self) -> bool {
2860 false
2861 }
2862 }
2863
2864 let present: Box<dyn Signer> = Box::new(keypair_from_seed(&[2u8; 32]).unwrap());
2865 let absent: Box<dyn Signer> = Box::new(NullSigner::new(&Pubkey::from([3u8; 32])));
2866 let bad: Box<dyn Signer> = Box::new(BadSigner::new(Pubkey::from([4u8; 32])));
2867 let to = Pubkey::from([5u8; 32]);
2868 let nonce = Pubkey::from([6u8; 32]);
2869 let from = present.pubkey();
2870 let fee_payer = absent.pubkey();
2871 let nonce_auth = bad.pubkey();
2872 let mut tx = Transaction::new_unsigned(Message::new_with_nonce(
2873 vec![system_instruction::transfer(&from, &to, 42)],
2874 Some(&fee_payer),
2875 &nonce,
2876 &nonce_auth,
2877 ));
2878
2879 let signers = vec![present.as_ref(), absent.as_ref(), bad.as_ref()];
2880 let blockhash = Hash::new(&[7u8; 32]);
2881 tx.try_partial_sign(&signers, blockhash).unwrap();
2882 let res = return_signers(&tx, &OutputFormat::JsonCompact).unwrap();
2883 let sign_only = parse_sign_only_reply_string(&res);
2884 assert_eq!(sign_only.blockhash, blockhash);
2885 assert_eq!(sign_only.message, None);
2886 assert_eq!(sign_only.present_signers[0].0, present.pubkey());
2887 assert_eq!(sign_only.absent_signers[0], absent.pubkey());
2888 assert_eq!(sign_only.bad_signers[0], bad.pubkey());
2889
2890 let res_data = return_signers_data(&tx, &ReturnSignersConfig::default());
2891 assert_eq!(
2892 res_data,
2893 CliSignOnlyData {
2894 blockhash: blockhash.to_string(),
2895 message: None,
2896 signers: vec![format!("{}={}", present.pubkey(), tx.signatures[1])],
2897 absent: vec![absent.pubkey().to_string()],
2898 bad_sig: vec![bad.pubkey().to_string()],
2899 }
2900 );
2901
2902 let expected_msg = "AwECBwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgTl3Dqh9\
2903 F19Wo1Rmw0x+zMuNipG07jeiXfYPW4/Js5QEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE\
2904 BAQEBAUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBgYGBgYGBgYGBgYGBgYGBgYG\
2905 BgYGBgYGBgYGBgYGBgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAan1RcZLFaO\
2906 4IqEX3PSl4jPA1wxRbIas0TYBi6pQAAABwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcHBwcH\
2907 BwcCBQMEBgIEBAAAAAUCAQMMAgAAACoAAAAAAAAA"
2908 .to_string();
2909 let config = ReturnSignersConfig {
2910 dump_transaction_message: true,
2911 };
2912 let res = return_signers_with_config(&tx, &OutputFormat::JsonCompact, &config).unwrap();
2913 let sign_only = parse_sign_only_reply_string(&res);
2914 assert_eq!(sign_only.blockhash, blockhash);
2915 assert_eq!(sign_only.message, Some(expected_msg.clone()));
2916 assert_eq!(sign_only.present_signers[0].0, present.pubkey());
2917 assert_eq!(sign_only.absent_signers[0], absent.pubkey());
2918 assert_eq!(sign_only.bad_signers[0], bad.pubkey());
2919
2920 let res_data = return_signers_data(&tx, &config);
2921 assert_eq!(
2922 res_data,
2923 CliSignOnlyData {
2924 blockhash: blockhash.to_string(),
2925 message: Some(expected_msg),
2926 signers: vec![format!("{}={}", present.pubkey(), tx.signatures[1])],
2927 absent: vec![absent.pubkey().to_string()],
2928 bad_sig: vec![bad.pubkey().to_string()],
2929 }
2930 );
2931 }
2932
2933 #[test]
2934 fn test_verbose_quiet_output_formats() {
2935 #[derive(Deserialize, Serialize)]
2936 struct FallbackToDisplay {}
2937 impl std::fmt::Display for FallbackToDisplay {
2938 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2939 write!(f, "display")
2940 }
2941 }
2942 impl QuietDisplay for FallbackToDisplay {}
2943 impl VerboseDisplay for FallbackToDisplay {}
2944
2945 let f = FallbackToDisplay {};
2946 assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
2947 assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "display");
2948 assert_eq!(
2949 &OutputFormat::DisplayVerbose.formatted_string(&f),
2950 "display"
2951 );
2952
2953 #[derive(Deserialize, Serialize)]
2954 struct DiscreteVerbosityDisplay {}
2955 impl std::fmt::Display for DiscreteVerbosityDisplay {
2956 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2957 write!(f, "display")
2958 }
2959 }
2960 impl QuietDisplay for DiscreteVerbosityDisplay {
2961 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
2962 write!(w, "quiet")
2963 }
2964 }
2965 impl VerboseDisplay for DiscreteVerbosityDisplay {
2966 fn write_str(&self, w: &mut dyn std::fmt::Write) -> std::fmt::Result {
2967 write!(w, "verbose")
2968 }
2969 }
2970
2971 let f = DiscreteVerbosityDisplay {};
2972 assert_eq!(&OutputFormat::Display.formatted_string(&f), "display");
2973 assert_eq!(&OutputFormat::DisplayQuiet.formatted_string(&f), "quiet");
2974 assert_eq!(
2975 &OutputFormat::DisplayVerbose.formatted_string(&f),
2976 "verbose"
2977 );
2978 }
2979
2980 #[test]
2981 fn test_output_format_from_matches() {
2982 let app = App::new("test").arg(
2983 Arg::with_name("output_format")
2984 .long("output")
2985 .value_name("FORMAT")
2986 .global(true)
2987 .takes_value(true)
2988 .possible_values(&["json", "json-compact"])
2989 .help("Return information in specified output format"),
2990 );
2991 let matches = app
2992 .clone()
2993 .get_matches_from(vec!["test", "--output", "json"]);
2994 assert_eq!(
2995 OutputFormat::from_matches(&matches, "output_format", false),
2996 OutputFormat::Json
2997 );
2998 assert_eq!(
2999 OutputFormat::from_matches(&matches, "output_format", true),
3000 OutputFormat::Json
3001 );
3002
3003 let matches = app
3004 .clone()
3005 .get_matches_from(vec!["test", "--output", "json-compact"]);
3006 assert_eq!(
3007 OutputFormat::from_matches(&matches, "output_format", false),
3008 OutputFormat::JsonCompact
3009 );
3010 assert_eq!(
3011 OutputFormat::from_matches(&matches, "output_format", true),
3012 OutputFormat::JsonCompact
3013 );
3014
3015 let matches = app.clone().get_matches_from(vec!["test"]);
3016 assert_eq!(
3017 OutputFormat::from_matches(&matches, "output_format", false),
3018 OutputFormat::Display
3019 );
3020 assert_eq!(
3021 OutputFormat::from_matches(&matches, "output_format", true),
3022 OutputFormat::DisplayVerbose
3023 );
3024 }
3025}