1use {
2 crate::cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
3 clap::{App, Arg, ArgMatches, SubCommand},
4 solana_clap_utils::{
5 input_parsers::{pubkeys_of, value_of},
6 input_validators::is_valid_pubkey,
7 keypair::*,
8 },
9 solana_cli_output::{
10 CliEpochRewardsMetadata, CliInflation, CliKeyedEpochReward, CliKeyedEpochRewards,
11 },
12 solana_clock::{Epoch, Slot, UnixTimestamp},
13 solana_pubkey::Pubkey,
14 solana_remote_wallet::remote_wallet::RemoteWalletManager,
15 solana_rpc_client::nonblocking::rpc_client::RpcClient,
16 std::{collections::HashMap, rc::Rc},
17};
18
19#[derive(Debug, PartialEq, Eq)]
20pub enum InflationCliCommand {
21 Show,
22 Rewards(Vec<Pubkey>, Option<Epoch>),
23}
24
25pub trait InflationSubCommands {
26 fn inflation_subcommands(self) -> Self;
27}
28
29impl InflationSubCommands for App<'_, '_> {
30 fn inflation_subcommands(self) -> Self {
31 self.subcommand(
32 SubCommand::with_name("inflation")
33 .about("Show inflation information")
34 .subcommand(
35 SubCommand::with_name("rewards")
36 .about("Show inflation rewards for a set of addresses")
37 .arg(pubkey!(
38 Arg::with_name("addresses")
39 .value_name("ADDRESS")
40 .index(1)
41 .multiple(true)
42 .required(true),
43 "Account to query for rewards."
44 ))
45 .arg(
46 Arg::with_name("rewards_epoch")
47 .long("rewards-epoch")
48 .takes_value(true)
49 .value_name("EPOCH")
50 .help("Display rewards for specific epoch [default: latest epoch]"),
51 ),
52 ),
53 )
54 }
55}
56
57pub fn parse_inflation_subcommand(
58 matches: &ArgMatches<'_>,
59 _default_signer: &DefaultSigner,
60 _wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
61) -> Result<CliCommandInfo, CliError> {
62 let command = match matches.subcommand() {
63 ("rewards", Some(matches)) => {
64 let addresses = pubkeys_of(matches, "addresses").unwrap();
65 let rewards_epoch = value_of(matches, "rewards_epoch");
66 InflationCliCommand::Rewards(addresses, rewards_epoch)
67 }
68 _ => InflationCliCommand::Show,
69 };
70 Ok(CliCommandInfo::without_signers(CliCommand::Inflation(
71 command,
72 )))
73}
74
75pub async fn process_inflation_subcommand(
76 rpc_client: &RpcClient,
77 config: &CliConfig<'_>,
78 inflation_subcommand: &InflationCliCommand,
79) -> ProcessResult {
80 match inflation_subcommand {
81 InflationCliCommand::Show => process_show(rpc_client, config).await,
82 InflationCliCommand::Rewards(addresses, rewards_epoch) => {
83 process_rewards(rpc_client, config, addresses, *rewards_epoch).await
84 }
85 }
86}
87
88async fn process_show(rpc_client: &RpcClient, config: &CliConfig<'_>) -> ProcessResult {
89 let governor = rpc_client.get_inflation_governor().await?;
90 let current_rate = rpc_client.get_inflation_rate().await?;
91
92 let inflation = CliInflation {
93 governor,
94 current_rate,
95 };
96
97 Ok(config.output_format.formatted_string(&inflation))
98}
99
100async fn process_rewards(
101 rpc_client: &RpcClient,
102 config: &CliConfig<'_>,
103 addresses: &[Pubkey],
104 rewards_epoch: Option<Epoch>,
105) -> ProcessResult {
106 let rewards = rpc_client
107 .get_inflation_reward(addresses, rewards_epoch)
108 .await
109 .map_err(|err| {
110 if let Some(epoch) = rewards_epoch {
111 format!("Rewards not available for epoch {epoch}")
112 } else {
113 format!("Rewards not available {err}")
114 }
115 })?;
116 let epoch_schedule = rpc_client.get_epoch_schedule().await?;
117
118 let mut epoch_rewards: Vec<CliKeyedEpochReward> = vec![];
119 let mut block_times: HashMap<Slot, UnixTimestamp> = HashMap::new();
120 let epoch_metadata = if let Some(Some(first_reward)) = rewards.iter().find(|&v| v.is_some()) {
121 let (epoch_start_time, epoch_end_time) =
122 crate::stake::get_epoch_boundary_timestamps(rpc_client, first_reward, &epoch_schedule)
123 .await?;
124 for (reward, address) in rewards.iter().zip(addresses) {
125 let cli_reward = if let Some(reward) = reward {
126 let block_time = if let Some(block_time) = block_times.get(&reward.effective_slot) {
127 *block_time
128 } else {
129 let block_time = rpc_client.get_block_time(reward.effective_slot).await?;
130 block_times.insert(reward.effective_slot, block_time);
131 block_time
132 };
133 crate::stake::make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
134 } else {
135 None
136 };
137 epoch_rewards.push(CliKeyedEpochReward {
138 address: address.to_string(),
139 reward: cli_reward,
140 });
141 }
142 Some(CliEpochRewardsMetadata {
143 epoch: first_reward.epoch,
144 ..CliEpochRewardsMetadata::default()
145 })
146 } else {
147 None
148 };
149 let cli_rewards = CliKeyedEpochRewards {
150 epoch_metadata,
151 rewards: epoch_rewards,
152 };
153 Ok(config.output_format.formatted_string(&cli_rewards))
154}