Skip to main content

solana_cli/
inflation.rs

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}