diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index 81266d9320..ec21fbe684 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -1528,6 +1528,100 @@ impl fmt::Display for CliProgramAuthority { } } +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliUpgradeableProgram { + pub program_id: String, + pub program_executable: bool, + pub program_lamports: u64, + pub programdata_address: String, + pub programdata_lamports: u64, + pub programdata_authority: String, + pub programdata_slot: u64, + pub programdata_data_len: usize, + pub programdata_program_len: usize, + pub use_lamports_unit: bool, +} +impl QuietDisplay for CliUpgradeableProgram {} +impl VerboseDisplay for CliUpgradeableProgram {} +impl fmt::Display for CliUpgradeableProgram { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f)?; + writeln_name_value(f, "Program Address:", &self.program_id)?; + writeln_name_value(f, " Executable:", &self.program_executable.to_string())?; + writeln_name_value( + f, + " Balance:", + &build_balance_message(self.program_lamports, self.use_lamports_unit, true), + )?; + writeln_name_value(f, " ProgramData Address:", &self.programdata_address)?; + writeln_name_value(f, " Authority:", &self.programdata_authority)?; + writeln_name_value( + f, + " Balance:", + &build_balance_message(self.programdata_lamports, self.use_lamports_unit, true), + )?; + writeln_name_value( + f, + " Total Length:", + &format!( + "{:?} ({:#x?}) bytes", + self.programdata_data_len, self.programdata_data_len + ), + )?; + writeln_name_value( + f, + " Program Length:", + &format!( + "{:?} ({:#x?}) bytes", + self.programdata_program_len, self.programdata_program_len + ), + )?; + writeln_name_value( + f, + " Last Upgraded In Slot:", + &self.programdata_slot.to_string(), + )?; + Ok(()) + } +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CliUpgradeableBuffer { + pub address: String, + pub lamports: u64, + pub authority: String, + pub data_len: usize, + pub program_len: usize, + pub use_lamports_unit: bool, +} +impl QuietDisplay for CliUpgradeableBuffer {} +impl VerboseDisplay for CliUpgradeableBuffer {} +impl fmt::Display for CliUpgradeableBuffer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + writeln!(f)?; + writeln_name_value(f, "Buffer Address:", &self.address)?; + writeln_name_value(f, " Authority:", &self.authority)?; + writeln_name_value( + f, + " Balance:", + &build_balance_message(self.lamports, self.use_lamports_unit, true), + )?; + writeln_name_value( + f, + " Total Length:", + &format!("{:?} ({:#x?}) bytes", self.data_len, self.data_len), + )?; + writeln_name_value( + f, + " Program Length:", + &format!("{:?} ({:#x?}) bytes", self.program_len, self.program_len), + )?; + Ok(()) + } +} + pub fn return_signers( tx: &Transaction, output_format: &OutputFormat, diff --git a/cli/src/program.rs b/cli/src/program.rs index c9bdd6f827..23ef7124d5 100644 --- a/cli/src/program.rs +++ b/cli/src/program.rs @@ -14,7 +14,7 @@ use solana_bpf_loader_program::{bpf_verifier, BPFError, ThisInstructionMeter}; use solana_clap_utils::{self, input_parsers::*, input_validators::*, keypair::*}; use solana_cli_output::{ display::new_spinner_progress_bar, CliProgramAccountType, CliProgramAuthority, - CliProgramBuffer, CliProgramId, + CliProgramBuffer, CliProgramId, CliUpgradeableBuffer, CliUpgradeableProgram, }; use solana_client::{ rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, @@ -89,6 +89,10 @@ pub enum ProgramCliCommand { GetAuthority { account_pubkey: Option, }, + GetInfo { + account_pubkey: Option, + use_lamports_unit: bool, + }, } pub trait ProgramSubCommands { @@ -266,6 +270,24 @@ impl ProgramSubCommands for App<'_, '_> { .help("Public key of the account to query") ), ) + .subcommand( + SubCommand::with_name("get-info") + .about("Display a buffer or program") + .arg( + Arg::with_name("account") + .index(1) + .value_name("ACCOUNT_ADDRESS") + .takes_value(true) + .required(true) + .help("Public key of the buffer or program to query") + ) + .arg( + Arg::with_name("lamports") + .long("lamports") + .takes_value(false) + .help("Display balance in lamports instead of SOL"), + ), + ) ) } } @@ -469,6 +491,13 @@ pub fn parse_program_subcommand( }), signers: vec![], }, + ("get-info", Some(matches)) => CliCommandInfo { + command: CliCommand::Program(ProgramCliCommand::GetInfo { + account_pubkey: pubkey_of(matches, "account"), + use_lamports_unit: matches.is_present("lamports"), + }), + signers: vec![], + }, _ => unreachable!(), }; Ok(response) @@ -545,6 +574,10 @@ pub fn process_program_subcommand( ProgramCliCommand::GetAuthority { account_pubkey } => { process_get_authority(&rpc_client, config, *account_pubkey) } + ProgramCliCommand::GetInfo { + account_pubkey, + use_lamports_unit, + } => process_get_info(&rpc_client, config, *account_pubkey, *use_lamports_unit), } } @@ -938,6 +971,82 @@ fn process_get_authority( } } +fn process_get_info( + rpc_client: &RpcClient, + config: &CliConfig, + account_pubkey: Option, + use_lamports_unit: bool, +) -> ProcessResult { + if let Some(account_pubkey) = account_pubkey { + if let Some(account) = rpc_client + .get_account_with_commitment(&account_pubkey, config.commitment)? + .value + { + if let Ok(UpgradeableLoaderState::Program { + programdata_address, + }) = account.state() + { + if let Some(programdata_account) = rpc_client + .get_account_with_commitment(&programdata_address, config.commitment)? + .value + { + if let Ok(UpgradeableLoaderState::ProgramData { + upgrade_authority_address, + slot, + }) = programdata_account.state() + { + Ok(config + .output_format + .formatted_string(&CliUpgradeableProgram { + program_id: account_pubkey.to_string(), + program_executable: account.executable, + program_lamports: account.lamports, + programdata_address: programdata_address.to_string(), + programdata_lamports: programdata_account.lamports, + programdata_authority: upgrade_authority_address + .map(|pubkey| pubkey.to_string()) + .unwrap_or_else(|| "none".to_string()), + programdata_slot: slot, + programdata_data_len: programdata_account.data.len(), + programdata_program_len: programdata_account.data.len() + - UpgradeableLoaderState::programdata_data_offset()?, + use_lamports_unit, + })) + } else { + Err("Invalid associated ProgramData account found for the program".into()) + } + } else { + Err( + "Failed to find associated ProgramData account for the provided program" + .into(), + ) + } + } else if let Ok(UpgradeableLoaderState::Buffer { authority_address }) = account.state() + { + Ok(config + .output_format + .formatted_string(&CliUpgradeableBuffer { + address: account_pubkey.to_string(), + lamports: account.lamports, + authority: authority_address + .map(|pubkey| pubkey.to_string()) + .unwrap_or_else(|| "none".to_string()), + data_len: account.data.len(), + program_len: account.data.len() + - UpgradeableLoaderState::buffer_data_offset()?, + use_lamports_unit, + })) + } else { + Err("Not a buffer or program account".into()) + } + } else { + Err("Unable to find the account".into()) + } + } else { + Err("No account specified".into()) + } +} + /// Deploy using non-upgradeable loader pub fn process_deploy( rpc_client: &RpcClient,