From cd63580b796319056edbbcca8690deb54c56581d Mon Sep 17 00:00:00 2001 From: JulI0-concerto <93286550+JulI0-concerto@users.noreply.github.com> Date: Wed, 10 Nov 2021 21:06:14 +0100 Subject: [PATCH] Implement multiple output format for spl-stake-pool list-all (#2558) * Implement json output formats for spl-stake-pool list-all * Implement --output json or json-compact or display in list-all cmd * cleanup: use destructuring instead of tuple * Fix related to PR comments * Add json and json-compact output for list command * implement list command display with Display and VerboseDisplay * make CliStakePool->details optional * cleanup use * Update stake-pool/cli/src/main.rs Co-authored-by: Tyera Eulberg * Update stake-pool/cli/src/main.rs Co-authored-by: Tyera Eulberg * clippished * fix use * fix fmt issue Co-authored-by: Julien Piccaluga Co-authored-by: Tyera Eulberg --- Cargo.lock | 3 + stake-pool/cli/Cargo.toml | 3 + stake-pool/cli/src/client.rs | 13 +- stake-pool/cli/src/main.rs | 227 ++++++----------- stake-pool/cli/src/output.rs | 481 +++++++++++++++++++++++++++++++++++ 5 files changed, 581 insertions(+), 146 deletions(-) create mode 100644 stake-pool/cli/src/output.rs diff --git a/Cargo.lock b/Cargo.lock index f1523b29550..d5d36affe3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3949,10 +3949,13 @@ dependencies = [ "borsh", "bs58 0.4.0", "clap", + "serde", + "serde_derive", "serde_json", "solana-account-decoder", "solana-clap-utils", "solana-cli-config", + "solana-cli-output", "solana-client", "solana-logger", "solana-program", diff --git a/stake-pool/cli/Cargo.toml b/stake-pool/cli/Cargo.toml index 2733d8fbe25..699f3cd7d76 100644 --- a/stake-pool/cli/Cargo.toml +++ b/stake-pool/cli/Cargo.toml @@ -11,10 +11,13 @@ version = "0.6.3" [dependencies] borsh = "0.9" clap = "2.33.3" +serde = "1.0.130" +serde_derive = "1.0.130" serde_json = "1.0.68" solana-account-decoder = "=1.8.1" solana-clap-utils = "=1.8.1" solana-cli-config = "=1.8.1" +solana-cli-output = "1.8.1" solana-client = "=1.8.1" solana-logger = "=1.8.1" solana-program = "=1.8.1" diff --git a/stake-pool/cli/src/client.rs b/stake-pool/cli/src/client.rs index 8fb0aa8b04a..c36a8956253 100644 --- a/stake-pool/cli/src/client.rs +++ b/stake-pool/cli/src/client.rs @@ -8,7 +8,10 @@ use { rpc_filter::{Memcmp, MemcmpEncodedBytes, RpcFilterType}, }, solana_program::{borsh::try_from_slice_unchecked, program_pack::Pack, pubkey::Pubkey, stake}, - spl_stake_pool::state::{StakePool, ValidatorList}, + spl_stake_pool::{ + find_withdraw_authority_program_address, + state::{StakePool, ValidatorList}, + }, }; type Error = Box; @@ -76,7 +79,7 @@ pub(crate) fn get_stake_state( pub(crate) fn get_stake_pools( rpc_client: &RpcClient, -) -> Result, ClientError> { +) -> Result, ClientError> { rpc_client .get_program_accounts_with_config( &spl_stake_pool::id(), @@ -97,10 +100,14 @@ pub(crate) fn get_stake_pools( accounts .into_iter() .filter_map(|(address, account)| { + let pool_withdraw_authority = + find_withdraw_authority_program_address(&spl_stake_pool::id(), &address).0; match try_from_slice_unchecked::(account.data.as_slice()) { Ok(stake_pool) => { get_validator_list(rpc_client, &stake_pool.validator_list) - .map(|v| (address, stake_pool, v)) + .map(|validator_list| { + (address, stake_pool, validator_list, pool_withdraw_authority) + }) .ok() } Err(err) => { diff --git a/stake-pool/cli/src/main.rs b/stake-pool/cli/src/main.rs index 6a690380d93..4247716e666 100644 --- a/stake-pool/cli/src/main.rs +++ b/stake-pool/cli/src/main.rs @@ -1,7 +1,11 @@ mod client; +mod output; use { - crate::client::*, + crate::{ + client::*, + output::{CliStakePool, CliStakePoolDetails, CliStakePoolStakeAccountInfo, CliStakePools}, + }, clap::{ crate_description, crate_name, crate_version, value_t, value_t_or_exit, App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand, @@ -14,6 +18,7 @@ use { }, keypair::{signer_from_path_with_config, SignerFromPathConfig}, }, + solana_cli_output::OutputFormat, solana_client::rpc_client::RpcClient, solana_program::{ borsh::{get_instance_packed_len, get_packed_len}, @@ -44,9 +49,10 @@ use { std::{process::exit, sync::Arc}, }; -struct Config { +pub(crate) struct Config { rpc_client: RpcClient, verbose: bool, + output_format: OutputFormat, manager: Box, staker: Box, funding_authority: Option>, @@ -852,96 +858,25 @@ fn command_deposit_sol( fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult { let stake_pool = get_stake_pool(&config.rpc_client, stake_pool_address)?; + let reserve_stake_account_address = stake_pool.reserve_stake.to_string(); + let total_lamports = stake_pool.total_lamports; + let last_update_epoch = stake_pool.last_update_epoch; let validator_list = get_validator_list(&config.rpc_client, &stake_pool.validator_list)?; + let max_number_of_validators = validator_list.header.max_validators; + let current_number_of_validators = validator_list.validators.len(); let pool_mint = get_token_mint(&config.rpc_client, &stake_pool.pool_mint)?; let epoch_info = config.rpc_client.get_epoch_info()?; let pool_withdraw_authority = find_withdraw_authority_program_address(&spl_stake_pool::id(), stake_pool_address).0; - let sol_deposit_authority = stake_pool - .sol_deposit_authority - .map_or("None".into(), |pubkey| pubkey.to_string()); - let sol_withdraw_authority = stake_pool - .sol_withdraw_authority - .map_or("None".into(), |pubkey| pubkey.to_string()); - - if config.verbose { - println!("Stake Pool Info"); - println!("==============="); - println!("Stake Pool: {}", stake_pool_address); - println!("Validator List: {}", stake_pool.validator_list); - println!("Manager: {}", stake_pool.manager); - println!("Staker: {}", stake_pool.staker); - println!("Depositor: {}", stake_pool.stake_deposit_authority); - println!("SOL Deposit Authority: {}", sol_deposit_authority); - println!("SOL Withdraw Authority: {}", sol_withdraw_authority); - println!("Withdraw Authority: {}", pool_withdraw_authority); - println!("Pool Token Mint: {}", stake_pool.pool_mint); - println!("Fee Account: {}", stake_pool.manager_fee_account); - } else { - println!("Stake Pool: {}", stake_pool_address); - println!("Validator List: {}", stake_pool.validator_list); - println!("Pool Token Mint: {}", stake_pool.pool_mint); - } - - if let Some(preferred_deposit_validator) = stake_pool.preferred_deposit_validator_vote_address { - println!( - "Preferred Deposit Validator: {}", - preferred_deposit_validator - ); - } - if let Some(preferred_withdraw_validator) = stake_pool.preferred_withdraw_validator_vote_address - { - println!( - "Preferred Withraw Validator: {}", - preferred_withdraw_validator - ); - } - - // Display fees information - println!("Epoch Fee: {} of epoch rewards", stake_pool.epoch_fee); - println!( - "Stake Withdrawal Fee: {} of withdrawal amount", - stake_pool.stake_withdrawal_fee - ); - println!( - "SOL Withdrawal Fee: {} of withdrawal amount", - stake_pool.sol_withdrawal_fee - ); - println!( - "Stake Deposit Fee: {} of deposit amount", - stake_pool.stake_deposit_fee - ); - println!( - "SOL Deposit Fee: {} of deposit amount", - stake_pool.sol_deposit_fee - ); - println!( - "Stake Deposit Referral Fee: {}% of Stake Deposit Fee", - stake_pool.stake_referral_fee - ); - println!( - "SOL Deposit Referral Fee: {}% of SOL Deposit Fee", - stake_pool.sol_referral_fee - ); - - if config.verbose { - println!(); - println!("Stake Accounts"); - println!("--------------"); - } let reserve_stake = config.rpc_client.get_account(&stake_pool.reserve_stake)?; let minimum_reserve_stake_balance = config .rpc_client .get_minimum_balance_for_rent_exemption(STAKE_STATE_LEN)? + 1; - println!( - "Reserve Account: {}\tAvailable Balance: {}", - stake_pool.reserve_stake, - Sol(reserve_stake.lamports - minimum_reserve_stake_balance), - ); - - for validator in &validator_list.validators { - if config.verbose { + let cli_stake_pool_stake_account_infos = validator_list + .validators + .iter() + .map(|validator| { let (stake_account_address, _) = find_stake_program_address( &spl_stake_pool::id(), &validator.vote_account_address, @@ -953,55 +888,42 @@ fn command_list(config: &Config, stake_pool_address: &Pubkey) -> CommandResult { stake_pool_address, validator.transient_seed_suffix_start, ); - println!( - "Vote Account: {}\tStake Account: {}\tActive Balance: {}\tTransient Stake Account: {}\tTransient Balance: {}\tLast Update Epoch: {}{}", - validator.vote_account_address, - stake_account_address, - Sol(validator.active_stake_lamports), - transient_stake_account_address, - Sol(validator.transient_stake_lamports), - validator.last_update_epoch, - if validator.last_update_epoch != epoch_info.epoch { - " [UPDATE REQUIRED]" - } else { - "" - } - ); - } else { - println!( - "Vote Account: {}\tBalance: {}\tLast Update Epoch: {}", - validator.vote_account_address, - Sol(validator.stake_lamports()), - validator.last_update_epoch, - ); - } - } - - if config.verbose { - println!(); - } - println!( - "Total Pool Stake: {}{}", - Sol(stake_pool.total_lamports), - if stake_pool.last_update_epoch != epoch_info.epoch { - " [UPDATE REQUIRED]" - } else { - "" - } - ); - println!( - "Total Pool Tokens: {}", - spl_token::amount_to_ui_amount(stake_pool.pool_token_supply, pool_mint.decimals) - ); - println!( - "Current Number of Validators: {}", - validator_list.validators.len() - ); - println!( - "Max Number of Validators: {}", - validator_list.header.max_validators - ); - + let update_required = validator.last_update_epoch != epoch_info.epoch; + CliStakePoolStakeAccountInfo { + vote_account_address: stake_pool_address.to_string(), + stake_account_address: stake_account_address.to_string(), + validator_active_stake_lamports: validator.active_stake_lamports, + validator_last_update_epoch: validator.last_update_epoch, + validator_lamports: validator.stake_lamports(), + validator_transient_stake_account_address: transient_stake_account_address + .to_string(), + validator_transient_stake_lamports: validator.transient_stake_lamports, + update_required, + } + }) + .collect(); + let total_pool_tokens = + spl_token::amount_to_ui_amount(stake_pool.pool_token_supply, pool_mint.decimals); + let mut cli_stake_pool = CliStakePool::from(( + *stake_pool_address, + stake_pool, + validator_list, + pool_withdraw_authority, + )); + let update_required = last_update_epoch != epoch_info.epoch; + let cli_stake_pool_details = CliStakePoolDetails { + reserve_stake_account_address, + reserve_stake_lamports: reserve_stake.lamports, + minimum_reserve_stake_balance, + stake_accounts: cli_stake_pool_stake_account_infos, + total_lamports, + total_pool_tokens, + current_number_of_validators: current_number_of_validators as u32, + max_number_of_validators, + update_required, + }; + cli_stake_pool.details = Some(cli_stake_pool_details); + println!("{}", config.output_format.formatted_string(&cli_stake_pool)); Ok(()) } @@ -1626,18 +1548,15 @@ fn command_set_fee( fn command_list_all_pools(config: &Config) -> CommandResult { let all_pools = get_stake_pools(&config.rpc_client)?; - let count = all_pools.len(); - for (address, stake_pool, validator_list) in all_pools { - println!( - "Address: {}\tManager: {}\tLamports: {}\tPool tokens: {}\tValidators: {}", - address, - stake_pool.manager, - stake_pool.total_lamports, - stake_pool.pool_token_supply, - validator_list.validators.len() - ); - } - println!("Total number of pools: {}", count); + let cli_stake_pool_vec: Vec = + all_pools.into_iter().map(CliStakePool::from).collect(); + let cli_stake_pools = CliStakePools { + pools: cli_stake_pool_vec, + }; + println!( + "{}", + config.output_format.formatted_string(&cli_stake_pools) + ); Ok(()) } @@ -1670,6 +1589,15 @@ fn main() { .global(true) .help("Show additional information"), ) + .arg( + Arg::with_name("output_format") + .long("output") + .value_name("FORMAT") + .global(true) + .takes_value(true) + .possible_values(&["json", "json-compact"]) + .help("Return information in specified output format"), + ) .arg( Arg::with_name("dry_run") .long("dry-run") @@ -2477,12 +2405,25 @@ fn main() { }, ); let verbose = matches.is_present("verbose"); + let output_format = matches + .value_of("output_format") + .map(|value| match value { + "json" => OutputFormat::Json, + "json-compact" => OutputFormat::JsonCompact, + _ => unreachable!(), + }) + .unwrap_or(if verbose { + OutputFormat::DisplayVerbose + } else { + OutputFormat::Display + }); let dry_run = matches.is_present("dry_run"); let no_update = matches.is_present("no_update"); Config { rpc_client: RpcClient::new_with_commitment(json_rpc_url, CommitmentConfig::confirmed()), verbose, + output_format, manager, staker, funding_authority, diff --git a/stake-pool/cli/src/output.rs b/stake-pool/cli/src/output.rs new file mode 100644 index 00000000000..bc198f11fdf --- /dev/null +++ b/stake-pool/cli/src/output.rs @@ -0,0 +1,481 @@ +use { + serde::{Deserialize, Serialize}, + solana_cli_output::{QuietDisplay, VerboseDisplay}, + solana_sdk::native_token::Sol, + solana_sdk::{pubkey::Pubkey, stake::state::Lockup}, + spl_stake_pool::state::{Fee, StakePool, StakeStatus, ValidatorList, ValidatorStakeInfo}, + std::fmt::{Display, Formatter, Result, Write}, +}; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CliStakePools { + pub pools: Vec, +} + +impl Display for CliStakePools { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + for pool in &self.pools { + writeln!( + f, + "Address: {}\tManager: {}\tLamports: {}\tPool tokens: {}\tValidators: {}", + pool.address, + pool.manager, + pool.total_lamports, + pool.pool_token_supply, + pool.validator_list.len() + )?; + } + writeln!(f, "Total number of pools: {}", &self.pools.len())?; + Ok(()) + } +} + +impl QuietDisplay for CliStakePools {} +impl VerboseDisplay for CliStakePools {} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CliStakePool { + pub address: String, + pub pool_withdraw_authority: String, + pub manager: String, + pub staker: String, + pub stake_deposit_authority: String, + pub stake_withdraw_bump_seed: u8, + pub max_validators: u32, + pub validator_list: Vec, + pub validator_list_storage_account: String, + pub reserve_stake: String, + pub pool_mint: String, + pub manager_fee_account: String, + pub token_program_id: String, + pub total_lamports: u64, + pub pool_token_supply: u64, + pub last_update_epoch: u64, + pub lockup: CliStakePoolLockup, + pub epoch_fee: CliStakePoolFee, + pub next_epoch_fee: Option, + pub preferred_deposit_validator_vote_address: Option, + pub preferred_withdraw_validator_vote_address: Option, + pub stake_deposit_fee: CliStakePoolFee, + pub stake_withdrawal_fee: CliStakePoolFee, + pub next_stake_withdrawal_fee: Option, + pub stake_referral_fee: u8, + pub sol_deposit_authority: Option, + pub sol_deposit_fee: CliStakePoolFee, + pub sol_referral_fee: u8, + pub sol_withdraw_authority: Option, + pub sol_withdrawal_fee: CliStakePoolFee, + pub next_sol_withdrawal_fee: Option, + pub last_epoch_pool_token_supply: u64, + pub last_epoch_total_lamports: u64, + pub details: Option, +} + +impl QuietDisplay for CliStakePool {} +impl VerboseDisplay for CliStakePool { + fn write_str(&self, w: &mut dyn Write) -> Result { + writeln!(w, "Stake Pool Info")?; + writeln!(w, "===============")?; + writeln!(w, "Stake Pool: {}", &self.address)?; + writeln!( + w, + "Validator List: {}", + &self.validator_list_storage_account + )?; + writeln!(w, "Manager: {}", &self.manager)?; + writeln!(w, "Staker: {}", &self.staker)?; + writeln!(w, "Depositor: {}", &self.stake_deposit_authority)?; + writeln!( + w, + "SOL Deposit Authority: {}", + &self + .sol_deposit_authority + .as_ref() + .unwrap_or(&"None".to_string()) + )?; + writeln!( + w, + "SOL Withdraw Authority: {}", + &self + .sol_withdraw_authority + .as_ref() + .unwrap_or(&"None".to_string()) + )?; + writeln!(w, "Withdraw Authority: {}", &self.pool_withdraw_authority)?; + writeln!(w, "Pool Token Mint: {}", &self.pool_mint)?; + writeln!(w, "Fee Account: {}", &self.manager_fee_account)?; + match &self.preferred_deposit_validator_vote_address { + None => {} + Some(s) => { + writeln!(w, "Preferred Deposit Validator: {}", s)?; + } + } + match &self.preferred_withdraw_validator_vote_address { + None => {} + Some(s) => { + writeln!(w, "Preferred Withraw Validator: {}", s)?; + } + } + writeln!(w, "Epoch Fee: {} of epoch rewards", &self.epoch_fee)?; + writeln!( + w, + "Stake Withdrawal Fee: {} of withdrawal amount", + &self.stake_withdrawal_fee + )?; + writeln!( + w, + "SOL Withdrawal Fee: {} of withdrawal amount", + &self.sol_withdrawal_fee + )?; + writeln!( + w, + "Stake Deposit Fee: {} of deposit amount", + &self.stake_deposit_fee + )?; + writeln!( + w, + "SOL Deposit Fee: {} of deposit amount", + &self.sol_deposit_fee + )?; + writeln!( + w, + "Stake Deposit Referral Fee: {}% of Stake Deposit Fee", + &self.stake_referral_fee + )?; + writeln!( + w, + "SOL Deposit Referral Fee: {}% of SOL Deposit Fee", + &self.sol_referral_fee + )?; + writeln!(w)?; + writeln!(w, "Stake Accounts")?; + writeln!(w, "--------------")?; + match &self.details { + None => {} + Some(details) => { + VerboseDisplay::write_str(details, w)?; + } + } + Ok(()) + } +} + +impl Display for CliStakePool { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + writeln!(f, "Stake Pool: {}", &self.address)?; + writeln!( + f, + "Validator List: {}", + &self.validator_list_storage_account + )?; + writeln!(f, "Pool Token Mint: {}", &self.pool_mint)?; + match &self.preferred_deposit_validator_vote_address { + None => {} + Some(s) => { + writeln!(f, "Preferred Deposit Validator: {}", s)?; + } + } + match &self.preferred_withdraw_validator_vote_address { + None => {} + Some(s) => { + writeln!(f, "Preferred Withraw Validator: {}", s)?; + } + } + writeln!(f, "Epoch Fee: {} of epoch rewards", &self.epoch_fee)?; + writeln!( + f, + "Stake Withdrawal Fee: {} of withdrawal amount", + &self.stake_withdrawal_fee + )?; + writeln!( + f, + "SOL Withdrawal Fee: {} of withdrawal amount", + &self.sol_withdrawal_fee + )?; + writeln!( + f, + "Stake Deposit Fee: {} of deposit amount", + &self.stake_deposit_fee + )?; + writeln!( + f, + "SOL Deposit Fee: {} of deposit amount", + &self.sol_deposit_fee + )?; + writeln!( + f, + "Stake Deposit Referral Fee: {}% of Stake Deposit Fee", + &self.stake_referral_fee + )?; + writeln!( + f, + "SOL Deposit Referral Fee: {}% of SOL Deposit Fee", + &self.sol_referral_fee + )?; + Ok(()) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CliStakePoolDetails { + pub reserve_stake_account_address: String, + pub reserve_stake_lamports: u64, + pub minimum_reserve_stake_balance: u64, + pub stake_accounts: Vec, + pub total_lamports: u64, + pub total_pool_tokens: f64, + pub current_number_of_validators: u32, + pub max_number_of_validators: u32, + pub update_required: bool, +} + +impl Display for CliStakePoolDetails { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + writeln!( + f, + "Reserve Account: {}\tAvailable Balance: {}", + &self.reserve_stake_account_address, + Sol(self.reserve_stake_lamports - self.minimum_reserve_stake_balance), + )?; + for stake_account in &self.stake_accounts { + writeln!( + f, + "Vote Account: {}\tBalance: {}\tLast Update Epoch: {}", + stake_account.vote_account_address, + Sol(stake_account.validator_lamports), + stake_account.validator_last_update_epoch, + )?; + } + writeln!( + f, + "Total Pool Stake: {} {}", + Sol(self.total_lamports), + if self.update_required { + " [UPDATE REQUIRED]" + } else { + "" + }, + )?; + writeln!(f, "Total Pool Tokens: {}", &self.total_pool_tokens,)?; + writeln!( + f, + "Current Number of Validators: {}", + &self.current_number_of_validators, + )?; + writeln!( + f, + "Max Number of Validators: {}", + &self.max_number_of_validators, + )?; + Ok(()) + } +} + +impl QuietDisplay for CliStakePoolDetails {} +impl VerboseDisplay for CliStakePoolDetails { + fn write_str(&self, w: &mut dyn Write) -> Result { + writeln!(w)?; + writeln!(w, "Stake Accounts")?; + writeln!(w, "--------------")?; + writeln!( + w, + "Reserve Account: {}\tAvailable Balance: {}", + &self.reserve_stake_account_address, + Sol(self.reserve_stake_lamports - self.minimum_reserve_stake_balance), + )?; + for stake_account in &self.stake_accounts { + writeln!( + w, + "Vote Account: {}\tStake Account: {}\tActive Balance: {}\tTransient Stake Account: {}\tTransient Balance: {}\tLast Update Epoch: {}{}", + stake_account.vote_account_address, + stake_account.stake_account_address, + Sol(stake_account.validator_active_stake_lamports), + stake_account.validator_transient_stake_account_address, + Sol(stake_account.validator_transient_stake_lamports), + stake_account.validator_last_update_epoch, + if stake_account.update_required { + " [UPDATE REQUIRED]" + } else { + "" + }, + )?; + } + writeln!( + w, + "Total Pool Stake: {} {}", + Sol(self.total_lamports), + if self.update_required { + " [UPDATE REQUIRED]" + } else { + "" + }, + )?; + writeln!(w, "Total Pool Tokens: {}", &self.total_pool_tokens,)?; + writeln!( + w, + "Current Number of Validators: {}", + &self.current_number_of_validators, + )?; + writeln!( + w, + "Max Number of Validators: {}", + &self.max_number_of_validators, + )?; + Ok(()) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CliStakePoolStakeAccountInfo { + pub vote_account_address: String, + pub stake_account_address: String, + pub validator_active_stake_lamports: u64, + pub validator_last_update_epoch: u64, + pub validator_lamports: u64, + pub validator_transient_stake_account_address: String, + pub validator_transient_stake_lamports: u64, + pub update_required: bool, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CliStakePoolValidator { + pub active_stake_lamports: u64, + pub transient_stake_lamports: u64, + pub last_update_epoch: u64, + pub transient_seed_suffix_start: u64, + pub transient_seed_suffix_end: u64, + pub status: CliStakePoolValidatorStakeStatus, + pub vote_account_address: String, +} + +impl From for CliStakePoolValidator { + fn from(v: ValidatorStakeInfo) -> Self { + Self { + active_stake_lamports: v.active_stake_lamports, + transient_stake_lamports: v.transient_stake_lamports, + last_update_epoch: v.last_update_epoch, + transient_seed_suffix_start: v.transient_seed_suffix_start, + transient_seed_suffix_end: v.transient_seed_suffix_end, + status: CliStakePoolValidatorStakeStatus::from(v.status), + vote_account_address: v.vote_account_address.to_string(), + } + } +} + +impl From for CliStakePoolValidatorStakeStatus { + fn from(s: StakeStatus) -> CliStakePoolValidatorStakeStatus { + match s { + StakeStatus::Active => CliStakePoolValidatorStakeStatus::Active, + StakeStatus::DeactivatingTransient => { + CliStakePoolValidatorStakeStatus::DeactivatingTransient + } + StakeStatus::ReadyForRemoval => CliStakePoolValidatorStakeStatus::ReadyForRemoval, + } + } +} + +#[derive(Serialize, Deserialize)] +pub(crate) enum CliStakePoolValidatorStakeStatus { + Active, + DeactivatingTransient, + ReadyForRemoval, +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliStakePoolLockup { + pub unix_timestamp: i64, + pub epoch: u64, + pub custodian: String, +} + +impl From for CliStakePoolLockup { + fn from(l: Lockup) -> Self { + Self { + unix_timestamp: l.unix_timestamp, + epoch: l.epoch, + custodian: l.custodian.to_string(), + } + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct CliStakePoolFee { + pub denominator: u64, + pub numerator: u64, +} + +impl Display for CliStakePoolFee { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "{}/{}", &self.numerator, &self.denominator) + } +} + +impl From for CliStakePoolFee { + fn from(f: Fee) -> Self { + Self { + denominator: f.denominator, + numerator: f.numerator, + } + } +} + +impl From<(Pubkey, StakePool, ValidatorList, Pubkey)> for CliStakePool { + fn from(s: (Pubkey, StakePool, ValidatorList, Pubkey)) -> Self { + let (address, stake_pool, validator_list, pool_withdraw_authority) = s; + Self { + address: address.to_string(), + pool_withdraw_authority: pool_withdraw_authority.to_string(), + manager: stake_pool.manager.to_string(), + staker: stake_pool.staker.to_string(), + stake_deposit_authority: stake_pool.stake_deposit_authority.to_string(), + stake_withdraw_bump_seed: stake_pool.stake_withdraw_bump_seed, + max_validators: validator_list.header.max_validators, + validator_list: validator_list + .validators + .into_iter() + .map(CliStakePoolValidator::from) + .collect(), + validator_list_storage_account: stake_pool.validator_list.to_string(), + reserve_stake: stake_pool.reserve_stake.to_string(), + pool_mint: stake_pool.pool_mint.to_string(), + manager_fee_account: stake_pool.manager_fee_account.to_string(), + token_program_id: stake_pool.token_program_id.to_string(), + total_lamports: stake_pool.total_lamports, + pool_token_supply: stake_pool.pool_token_supply, + last_update_epoch: stake_pool.last_update_epoch, + lockup: CliStakePoolLockup::from(stake_pool.lockup), + epoch_fee: CliStakePoolFee::from(stake_pool.epoch_fee), + next_epoch_fee: stake_pool.next_epoch_fee.map(CliStakePoolFee::from), + preferred_deposit_validator_vote_address: stake_pool + .preferred_deposit_validator_vote_address + .map(|x| x.to_string()), + preferred_withdraw_validator_vote_address: stake_pool + .preferred_withdraw_validator_vote_address + .map(|x| x.to_string()), + stake_deposit_fee: CliStakePoolFee::from(stake_pool.stake_deposit_fee), + stake_withdrawal_fee: CliStakePoolFee::from(stake_pool.stake_withdrawal_fee), + next_stake_withdrawal_fee: stake_pool + .next_sol_withdrawal_fee + .map(CliStakePoolFee::from), + stake_referral_fee: stake_pool.stake_referral_fee, + sol_deposit_authority: stake_pool.sol_deposit_authority.map(|x| x.to_string()), + sol_deposit_fee: CliStakePoolFee::from(stake_pool.stake_deposit_fee), + sol_referral_fee: stake_pool.sol_referral_fee, + sol_withdraw_authority: stake_pool.sol_deposit_authority.map(|x| x.to_string()), + sol_withdrawal_fee: CliStakePoolFee::from(stake_pool.sol_withdrawal_fee), + next_sol_withdrawal_fee: stake_pool + .next_sol_withdrawal_fee + .map(CliStakePoolFee::from), + last_epoch_pool_token_supply: stake_pool.last_epoch_pool_token_supply, + last_epoch_total_lamports: stake_pool.last_epoch_total_lamports, + details: None, + } + } +}