Add csv output option to stake and vote account rewards (#32360)
* Add csv option to vote-account * Add CSV format to solana stake command Csv rename
This commit is contained in:
parent
3e8ccbe196
commit
3b1cbaec72
|
@ -10,7 +10,7 @@ use {
|
|||
QuietDisplay, VerboseDisplay,
|
||||
},
|
||||
base64::{prelude::BASE64_STANDARD, Engine},
|
||||
chrono::{Local, TimeZone},
|
||||
chrono::{Local, TimeZone, Utc},
|
||||
clap::ArgMatches,
|
||||
console::{style, Emoji},
|
||||
inflector::cases::titlecase::to_title_case,
|
||||
|
@ -1140,9 +1140,36 @@ fn show_votes_and_credits(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
enum Format {
|
||||
Csv,
|
||||
Human,
|
||||
}
|
||||
|
||||
macro_rules! format_as {
|
||||
($target:expr, $fmt1:expr, $fmt2:expr, $which_fmt:expr, $($arg:tt)*) => {
|
||||
match $which_fmt {
|
||||
Format::Csv => {
|
||||
writeln!(
|
||||
$target,
|
||||
$fmt1,
|
||||
$($arg)*
|
||||
)
|
||||
},
|
||||
Format::Human => {
|
||||
writeln!(
|
||||
$target,
|
||||
$fmt2,
|
||||
$($arg)*
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn show_epoch_rewards(
|
||||
f: &mut fmt::Formatter,
|
||||
epoch_rewards: &Option<Vec<CliEpochReward>>,
|
||||
use_csv: bool,
|
||||
) -> fmt::Result {
|
||||
if let Some(epoch_rewards) = epoch_rewards {
|
||||
if epoch_rewards.is_empty() {
|
||||
|
@ -1150,9 +1177,12 @@ fn show_epoch_rewards(
|
|||
}
|
||||
|
||||
writeln!(f, "Epoch Rewards:")?;
|
||||
writeln!(
|
||||
let fmt = if use_csv { Format::Csv } else { Format::Human };
|
||||
format_as!(
|
||||
f,
|
||||
"{},{},{},{},{},{},{},{}",
|
||||
" {:<6} {:<11} {:<26} {:<18} {:<18} {:>14} {:>14} {:>10}",
|
||||
fmt,
|
||||
"Epoch",
|
||||
"Reward Slot",
|
||||
"Time",
|
||||
|
@ -1160,15 +1190,17 @@ fn show_epoch_rewards(
|
|||
"New Balance",
|
||||
"Percent Change",
|
||||
"APR",
|
||||
"Commission"
|
||||
"Commission",
|
||||
)?;
|
||||
for reward in epoch_rewards {
|
||||
writeln!(
|
||||
format_as!(
|
||||
f,
|
||||
" {:<6} {:<11} {:<26} ◎{:<17.9} ◎{:<17.9} {:>13.6}% {:>14} {:>10}",
|
||||
"{},{},{},{},{},{}%,{},{}",
|
||||
" {:<6} {:<11} {:<26} ◎{:<17.9} ◎{:<17.9} {:>13.3}% {:>14} {:>10}",
|
||||
fmt,
|
||||
reward.epoch,
|
||||
reward.effective_slot,
|
||||
Local.timestamp_opt(reward.block_time, 0).unwrap(),
|
||||
Utc.timestamp_opt(reward.block_time, 0).unwrap(),
|
||||
lamports_to_sol(reward.amount),
|
||||
lamports_to_sol(reward.post_balance),
|
||||
reward.percent_change,
|
||||
|
@ -1219,6 +1251,8 @@ pub struct CliStakeState {
|
|||
pub deactivating_stake: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epoch_rewards: Option<Vec<CliEpochReward>>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_csv: bool,
|
||||
}
|
||||
|
||||
impl QuietDisplay for CliStakeState {}
|
||||
|
@ -1373,7 +1407,7 @@ impl fmt::Display for CliStakeState {
|
|||
}
|
||||
show_authorized(f, self.authorized.as_ref().unwrap())?;
|
||||
show_lockup(f, self.lockup.as_ref())?;
|
||||
show_epoch_rewards(f, &self.epoch_rewards)?
|
||||
show_epoch_rewards(f, &self.epoch_rewards, self.use_csv)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -1562,6 +1596,8 @@ pub struct CliVoteAccount {
|
|||
pub epoch_voting_history: Vec<CliEpochVotingHistory>,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_lamports_unit: bool,
|
||||
#[serde(skip_serializing)]
|
||||
pub use_csv: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub epoch_rewards: Option<Vec<CliEpochReward>>,
|
||||
}
|
||||
|
@ -1596,7 +1632,7 @@ impl fmt::Display for CliVoteAccount {
|
|||
self.recent_timestamp.slot
|
||||
)?;
|
||||
show_votes_and_credits(f, &self.votes, &self.epoch_voting_history)?;
|
||||
show_epoch_rewards(f, &self.epoch_rewards)?;
|
||||
show_epoch_rewards(f, &self.epoch_rewards, self.use_csv)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -3228,7 +3264,7 @@ mod tests {
|
|||
effective_slot: 100,
|
||||
epoch: 1,
|
||||
amount: 10,
|
||||
block_time: UnixTimestamp::default(),
|
||||
block_time: 0,
|
||||
apr: Some(10.0),
|
||||
},
|
||||
CliEpochReward {
|
||||
|
@ -3238,19 +3274,25 @@ mod tests {
|
|||
effective_slot: 200,
|
||||
epoch: 2,
|
||||
amount: 12,
|
||||
block_time: UnixTimestamp::default(),
|
||||
block_time: 1_000_000,
|
||||
apr: Some(13.0),
|
||||
},
|
||||
];
|
||||
|
||||
let c = CliVoteAccount {
|
||||
let mut c = CliVoteAccount {
|
||||
account_balance: 10000,
|
||||
validator_identity: Pubkey::default().to_string(),
|
||||
epoch_rewards: Some(epoch_rewards),
|
||||
recent_timestamp: BlockTimestamp::default(),
|
||||
..CliVoteAccount::default()
|
||||
};
|
||||
let s = format!("{c}");
|
||||
assert!(!s.is_empty());
|
||||
assert_eq!(s, "Account Balance: 0.00001 SOL\nValidator Identity: 11111111111111111111111111111111\nVote Authority: {}\nWithdraw Authority: \nCredits: 0\nCommission: 0%\nRoot Slot: ~\nRecent Timestamp: 1970-01-01T00:00:00Z from slot 0\nEpoch Rewards:\n Epoch Reward Slot Time Amount New Balance Percent Change APR Commission\n 1 100 1970-01-01 00:00:00 UTC ◎0.000000010 ◎0.000000100 11.000% 10.00% 1%\n 2 200 1970-01-12 13:46:40 UTC ◎0.000000012 ◎0.000000100 11.000% 13.00% 1%\n");
|
||||
println!("{s}");
|
||||
|
||||
c.use_csv = true;
|
||||
let s = format!("{c}");
|
||||
assert_eq!(s, "Account Balance: 0.00001 SOL\nValidator Identity: 11111111111111111111111111111111\nVote Authority: {}\nWithdraw Authority: \nCredits: 0\nCommission: 0%\nRoot Slot: ~\nRecent Timestamp: 1970-01-01T00:00:00Z from slot 0\nEpoch Rewards:\nEpoch,Reward Slot,Time,Amount,New Balance,Percent Change,APR,Commission\n1,100,1970-01-01 00:00:00 UTC,0.00000001,0.0000001,11%,10.00%,1%\n2,200,1970-01-12 13:46:40 UTC,0.000000012,0.0000001,11%,13.00%,1%\n");
|
||||
println!("{s}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -261,6 +261,7 @@ pub enum CliCommand {
|
|||
pubkey: Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
with_rewards: Option<usize>,
|
||||
use_csv: bool,
|
||||
},
|
||||
StakeAuthorize {
|
||||
stake_account_pubkey: Pubkey,
|
||||
|
@ -333,6 +334,7 @@ pub enum CliCommand {
|
|||
ShowVoteAccount {
|
||||
pubkey: Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
use_csv: bool,
|
||||
with_rewards: Option<usize>,
|
||||
},
|
||||
WithdrawFromVoteAccount {
|
||||
|
@ -1277,12 +1279,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||
pubkey: stake_account_pubkey,
|
||||
use_lamports_unit,
|
||||
with_rewards,
|
||||
use_csv,
|
||||
} => process_show_stake_account(
|
||||
&rpc_client,
|
||||
config,
|
||||
stake_account_pubkey,
|
||||
*use_lamports_unit,
|
||||
*with_rewards,
|
||||
*use_csv,
|
||||
),
|
||||
CliCommand::ShowStakeHistory {
|
||||
use_lamports_unit,
|
||||
|
@ -1441,12 +1445,14 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
|||
CliCommand::ShowVoteAccount {
|
||||
pubkey: vote_account_pubkey,
|
||||
use_lamports_unit,
|
||||
use_csv,
|
||||
with_rewards,
|
||||
} => process_show_vote_account(
|
||||
&rpc_client,
|
||||
config,
|
||||
vote_account_pubkey,
|
||||
*use_lamports_unit,
|
||||
*use_csv,
|
||||
*with_rewards,
|
||||
),
|
||||
CliCommand::WithdrawFromVoteAccount {
|
||||
|
|
|
@ -1821,6 +1821,7 @@ pub fn process_show_stakes(
|
|||
&stake_history,
|
||||
&clock,
|
||||
new_rate_activation_epoch,
|
||||
false,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -1840,6 +1841,7 @@ pub fn process_show_stakes(
|
|||
&stake_history,
|
||||
&clock,
|
||||
new_rate_activation_epoch,
|
||||
false,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -706,12 +706,18 @@ impl StakeSubCommands for App<'_, '_> {
|
|||
.takes_value(false)
|
||||
.help("Display inflation rewards"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("csv")
|
||||
.long("csv")
|
||||
.takes_value(false)
|
||||
.help("Format stake account data in csv")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_rewards_epochs")
|
||||
.long("num-rewards-epochs")
|
||||
.takes_value(true)
|
||||
.value_name("NUM")
|
||||
.validator(|s| is_within_range(s, 1..=10))
|
||||
.validator(|s| is_within_range(s, 1..=50))
|
||||
.default_value_if("with_rewards", None, "1")
|
||||
.requires("with_rewards")
|
||||
.help("Display rewards for NUM recent epochs, max 10 [default: latest epoch only]"),
|
||||
|
@ -1293,6 +1299,7 @@ pub fn parse_show_stake_account(
|
|||
let stake_account_pubkey =
|
||||
pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let use_csv = matches.is_present("csv");
|
||||
let with_rewards = if matches.is_present("with_rewards") {
|
||||
Some(value_of(matches, "num_rewards_epochs").unwrap())
|
||||
} else {
|
||||
|
@ -1303,6 +1310,7 @@ pub fn parse_show_stake_account(
|
|||
pubkey: stake_account_pubkey,
|
||||
use_lamports_unit,
|
||||
with_rewards,
|
||||
use_csv,
|
||||
},
|
||||
signers: vec![],
|
||||
})
|
||||
|
@ -2226,6 +2234,7 @@ pub fn build_stake_state(
|
|||
stake_history: &StakeHistory,
|
||||
clock: &Clock,
|
||||
new_rate_activation_epoch: Option<Epoch>,
|
||||
use_csv: bool,
|
||||
) -> CliStakeState {
|
||||
match stake_state {
|
||||
StakeStateV2::Stake(
|
||||
|
@ -2282,6 +2291,7 @@ pub fn build_stake_state(
|
|||
active_stake: u64_some_if_not_zero(effective),
|
||||
activating_stake: u64_some_if_not_zero(activating),
|
||||
deactivating_stake: u64_some_if_not_zero(deactivating),
|
||||
use_csv,
|
||||
..CliStakeState::default()
|
||||
}
|
||||
}
|
||||
|
@ -2448,6 +2458,7 @@ pub fn process_show_stake_account(
|
|||
stake_account_address: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
with_rewards: Option<usize>,
|
||||
use_csv: bool,
|
||||
) -> ProcessResult {
|
||||
let stake_account = rpc_client.get_account(stake_account_address)?;
|
||||
if stake_account.owner != stake::program::id() {
|
||||
|
@ -2478,6 +2489,7 @@ pub fn process_show_stake_account(
|
|||
&stake_history,
|
||||
&clock,
|
||||
new_rate_activation_epoch,
|
||||
use_csv,
|
||||
);
|
||||
|
||||
if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() {
|
||||
|
|
|
@ -333,12 +333,18 @@ impl VoteSubCommands for App<'_, '_> {
|
|||
.takes_value(false)
|
||||
.help("Display inflation rewards"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("csv")
|
||||
.long("csv")
|
||||
.takes_value(false)
|
||||
.help("Format rewards in a CSV table"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("num_rewards_epochs")
|
||||
.long("num-rewards-epochs")
|
||||
.takes_value(true)
|
||||
.value_name("NUM")
|
||||
.validator(|s| is_within_range(s, 1..=10))
|
||||
.validator(|s| is_within_range(s, 1..=50))
|
||||
.default_value_if("with_rewards", None, "1")
|
||||
.requires("with_rewards")
|
||||
.help("Display rewards for NUM recent epochs, max 10 [default: latest epoch only]"),
|
||||
|
@ -648,6 +654,7 @@ pub fn parse_vote_get_account_command(
|
|||
let vote_account_pubkey =
|
||||
pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
|
||||
let use_lamports_unit = matches.is_present("lamports");
|
||||
let use_csv = matches.is_present("csv");
|
||||
let with_rewards = if matches.is_present("with_rewards") {
|
||||
Some(value_of(matches, "num_rewards_epochs").unwrap())
|
||||
} else {
|
||||
|
@ -657,6 +664,7 @@ pub fn parse_vote_get_account_command(
|
|||
command: CliCommand::ShowVoteAccount {
|
||||
pubkey: vote_account_pubkey,
|
||||
use_lamports_unit,
|
||||
use_csv,
|
||||
with_rewards,
|
||||
},
|
||||
signers: vec![],
|
||||
|
@ -1208,6 +1216,7 @@ pub fn process_show_vote_account(
|
|||
config: &CliConfig,
|
||||
vote_account_address: &Pubkey,
|
||||
use_lamports_unit: bool,
|
||||
use_csv: bool,
|
||||
with_rewards: Option<usize>,
|
||||
) -> ProcessResult {
|
||||
let (vote_account, vote_state) =
|
||||
|
@ -1257,6 +1266,7 @@ pub fn process_show_vote_account(
|
|||
votes,
|
||||
epoch_voting_history,
|
||||
use_lamports_unit,
|
||||
use_csv,
|
||||
epoch_rewards,
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue