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:
sakridge 2023-09-21 13:23:37 -04:00 committed by GitHub
parent 3e8ccbe196
commit 3b1cbaec72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 86 additions and 14 deletions

View File

@ -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}");
}
}

View File

@ -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 {

View File

@ -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,
),
});
}

View File

@ -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() {

View File

@ -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,
};