From 12410541a41748bac468abbaee7a5eff21128834 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Wed, 20 Jan 2021 23:24:26 -0700 Subject: [PATCH] CLI: Add `calculate-rent` subcommand --- cli/src/cli.rs | 20 +++++++++- cli/src/cluster_query.rs | 80 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index fa05ede2a3..332ddfeb97 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -123,6 +123,10 @@ pub enum CliCommand { blockhash: Option, print_timestamp: bool, }, + Rent { + data_length: usize, + use_lamports_unit: bool, + }, ShowBlockProduction { epoch: Option, slot_limit: Option, @@ -864,6 +868,17 @@ pub fn parse_command( signers: signer_info.signers, }) } + ("rent", Some(matches)) => { + let data_length = value_of(matches, "data_length").unwrap(); + let use_lamports_unit = matches.is_present("lamports"); + Ok(CliCommandInfo { + command: CliCommand::Rent { + data_length, + use_lamports_unit, + }, + signers: vec![], + }) + } // ("", None) => { eprintln!("{}", matches.usage()); @@ -1172,7 +1187,6 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Cluster Query Commands // Get address of this client CliCommand::Address => Ok(format!("{}", config.pubkey()?)), - // Return software version of solana-cli and cluster entrypoint node CliCommand::Catchup { node_pubkey, @@ -1237,6 +1251,10 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { blockhash, *print_timestamp, ), + CliCommand::Rent { + data_length, + use_lamports_unit, + } => process_calculate_rent(&rpc_client, config, *data_length, *use_lamports_unit), CliCommand::ShowBlockProduction { epoch, slot_limit } => { process_show_block_production(&rpc_client, config, *epoch, *slot_limit) } diff --git a/cli/src/cluster_query.rs b/cli/src/cluster_query.rs index 3841d24270..1db08aeca9 100644 --- a/cli/src/cluster_query.rs +++ b/cli/src/cluster_query.rs @@ -6,6 +6,7 @@ use crate::{ use chrono::{Local, TimeZone}; use clap::{value_t, value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand}; use console::{style, Emoji}; +use serde::{Deserialize, Serialize}; use solana_clap_utils::{ input_parsers::*, input_validators::*, @@ -14,7 +15,8 @@ use solana_clap_utils::{ }; use solana_cli_output::{ display::{ - format_labeled_address, new_spinner_progress_bar, println_name_value, println_transaction, + build_balance_message, format_labeled_address, new_spinner_progress_bar, + println_name_value, println_transaction, writeln_name_value, }, *, }; @@ -40,6 +42,7 @@ use solana_sdk::{ message::Message, native_token::lamports_to_sol, pubkey::{self, Pubkey}, + rent::Rent, rpc_port::DEFAULT_RPC_PORT_STR, signature::Signature, system_instruction, system_program, @@ -47,11 +50,13 @@ use solana_sdk::{ self, stake_history::{self}, }, + timing, transaction::Transaction, }; use solana_transaction_status::UiTransactionEncoding; use std::{ collections::{BTreeMap, HashMap, VecDeque}, + fmt, net::SocketAddr, sync::{ atomic::{AtomicBool, Ordering}, @@ -376,6 +381,23 @@ impl ClusterQuerySubCommands for App<'_, '_> { .index(1), ), ) + .subcommand( + SubCommand::with_name("rent") + .about("Calculate per-epoch and rent-exempt-minimum values for a given account data length.") + .arg( + Arg::with_name("data_length") + .index(1) + .value_name("DATA_LENGTH") + .required(true) + .help("Length of data in the account to calculate rent for"), + ) + .arg( + Arg::with_name("lamports") + .long("lamports") + .takes_value(false) + .help("Display rent in lamports instead of SOL"), + ), + ) } } @@ -1827,6 +1849,62 @@ pub fn process_transaction_history( Ok(transactions_found) } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct CliRentCalculation { + pub lamports_per_byte_year: u64, + pub lamports_per_epoch: u64, + pub rent_exempt_minimum_lamports: u64, + #[serde(skip)] + pub use_lamports_unit: bool, +} + +impl CliRentCalculation { + fn build_balance_message(&self, lamports: u64) -> String { + build_balance_message(lamports, self.use_lamports_unit, true) + } +} + +impl fmt::Display for CliRentCalculation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let per_byte_year = self.build_balance_message(self.lamports_per_byte_year); + let per_epoch = self.build_balance_message(self.lamports_per_epoch); + let exempt_minimum = self.build_balance_message(self.rent_exempt_minimum_lamports); + writeln_name_value(f, "Rent per byte-year:", &per_byte_year)?; + writeln_name_value(f, "Rent per epoch:", &per_epoch)?; + writeln_name_value(f, "Rent-exempt minimum:", &exempt_minimum) + } +} + +impl QuietDisplay for CliRentCalculation {} +impl VerboseDisplay for CliRentCalculation {} + +pub fn process_calculate_rent( + rpc_client: &RpcClient, + config: &CliConfig, + data_length: usize, + use_lamports_unit: bool, +) -> ProcessResult { + let epoch_schedule = rpc_client.get_epoch_schedule()?; + let rent_account = rpc_client.get_account(&sysvar::rent::id())?; + let rent: Rent = rent_account.deserialize_data()?; + let rent_exempt_minimum_lamports = rent.minimum_balance(data_length); + let seconds_per_tick = Duration::from_secs_f64(1.0 / clock::DEFAULT_TICKS_PER_SECOND as f64); + let slots_per_year = + timing::years_as_slots(1.0, &seconds_per_tick, clock::DEFAULT_TICKS_PER_SLOT); + let slots_per_epoch = epoch_schedule.slots_per_epoch as f64; + let years_per_epoch = slots_per_epoch / slots_per_year; + let (lamports_per_epoch, _) = rent.due(0, data_length, years_per_epoch); + let cli_rent_calculation = CliRentCalculation { + lamports_per_byte_year: rent.lamports_per_byte_year, + lamports_per_epoch, + rent_exempt_minimum_lamports, + use_lamports_unit, + }; + + Ok(config.output_format.formatted_string(&cli_rent_calculation)) +} + #[cfg(test)] mod tests { use super::*;