1#[cfg(not(feature = "dev-context-only-utils"))]
2use solana_tps_client::utils::create_connection_cache;
3#[cfg(feature = "dev-context-only-utils")]
4use solana_tps_client::utils::create_connection_cache_for_tests;
5use {
6 crate::{
7 address_lookup_table::*, clap_app::*, cluster_query::*, feature::*, inflation::*, nonce::*,
8 program::*, program_v4::*, spend_utils::*, stake::*, validator_info::*, vote::*, wallet::*,
9 },
10 clap::{crate_description, crate_name, value_t_or_exit, ArgMatches, Shell},
11 log::*,
12 num_traits::FromPrimitive,
13 serde_json::{self, Value},
14 solana_clap_utils::{self, input_parsers::*, keypair::*},
15 solana_cli_config::ConfigInput,
16 solana_cli_output::{
17 display::println_name_value, CliSignature, CliValidatorsSortOrder, OutputFormat,
18 },
19 solana_client::connection_cache::ConnectionCache,
20 solana_clock::{Epoch, Slot},
21 solana_commitment_config::CommitmentConfig,
22 solana_hash::Hash,
23 solana_instruction::error::InstructionError,
24 solana_keypair::{read_keypair_file, Keypair},
25 solana_offchain_message::OffchainMessage,
26 solana_pubkey::Pubkey,
27 solana_remote_wallet::remote_wallet::RemoteWalletManager,
28 solana_rpc_client::rpc_client::RpcClient,
29 solana_rpc_client_api::{
30 client_error::{Error as ClientError, Result as ClientResult},
31 config::{RpcLargestAccountsFilter, RpcSendTransactionConfig, RpcTransactionLogsFilter},
32 },
33 solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
34 solana_signature::Signature,
35 solana_signer::{Signer, SignerError},
36 solana_stake_interface::{instruction::LockupArgs, state::Lockup},
37 solana_tps_client::TpsClient,
38 solana_tpu_client::tpu_client::{
39 TpuClient, TpuClientConfig, DEFAULT_TPU_CONNECTION_POOL_SIZE, DEFAULT_TPU_ENABLE_UDP,
40 },
41 solana_transaction::versioned::VersionedTransaction,
42 solana_transaction_error::TransactionError,
43 solana_vote_program::vote_state::VoteAuthorize,
44 std::{
45 collections::HashMap, error, io::stdout, process::exit, rc::Rc, str::FromStr, sync::Arc,
46 time::Duration,
47 },
48 thiserror::Error,
49};
50
51pub const DEFAULT_RPC_TIMEOUT_SECONDS: &str = "30";
52pub const DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS: &str = "5";
53const CHECKED: bool = true;
54pub const DEFAULT_PING_USE_TPU_CLIENT: bool = false;
55
56#[derive(Debug, PartialEq)]
57#[allow(clippy::large_enum_variant)]
58pub enum CliCommand {
59 Catchup {
61 node_pubkey: Option<Pubkey>,
62 node_json_rpc_url: Option<String>,
63 follow: bool,
64 our_localhost_port: Option<u16>,
65 log: bool,
66 },
67 ClusterDate,
68 ClusterVersion,
69 Feature(FeatureCliCommand),
70 Inflation(InflationCliCommand),
71 FindProgramDerivedAddress {
72 seeds: Vec<Vec<u8>>,
73 program_id: Pubkey,
74 },
75 FirstAvailableBlock,
76 GetBlock {
77 slot: Option<Slot>,
78 },
79 GetRecentPrioritizationFees {
80 accounts: Vec<Pubkey>,
81 limit_num_slots: Option<Slot>,
82 },
83 GetBlockTime {
84 slot: Option<Slot>,
85 },
86 GetEpoch,
87 GetEpochInfo,
88 GetGenesisHash,
89 GetSlot,
90 GetBlockHeight,
91 GetTransactionCount,
92 LargestAccounts {
93 filter: Option<RpcLargestAccountsFilter>,
94 },
95 LeaderSchedule {
96 epoch: Option<Epoch>,
97 },
98 LiveSlots,
99 Logs {
100 filter: RpcTransactionLogsFilter,
101 },
102 Ping {
103 interval: Duration,
104 count: Option<u64>,
105 timeout: Duration,
106 blockhash: Option<Hash>,
107 print_timestamp: bool,
108 compute_unit_price: Option<u64>,
109 },
110 Rent {
111 data_length: usize,
112 use_lamports_unit: bool,
113 },
114 ShowBlockProduction {
115 epoch: Option<Epoch>,
116 slot_limit: Option<u64>,
117 },
118 ShowGossip,
119 ShowStakes {
120 use_lamports_unit: bool,
121 vote_account_pubkeys: Option<Vec<Pubkey>>,
122 withdraw_authority: Option<Pubkey>,
123 },
124 ShowValidators {
125 use_lamports_unit: bool,
126 sort_order: CliValidatorsSortOrder,
127 reverse_sort: bool,
128 number_validators: bool,
129 keep_unstaked_delinquents: bool,
130 delinquent_slot_distance: Option<Slot>,
131 },
132 Supply {
133 print_accounts: bool,
134 },
135 TotalSupply,
136 TransactionHistory {
137 address: Pubkey,
138 before: Option<Signature>,
139 until: Option<Signature>,
140 limit: usize,
141 show_transactions: bool,
142 },
143 WaitForMaxStake {
144 max_stake_percent: f32,
145 },
146 AuthorizeNonceAccount {
148 nonce_account: Pubkey,
149 nonce_authority: SignerIndex,
150 memo: Option<String>,
151 new_authority: Pubkey,
152 compute_unit_price: Option<u64>,
153 },
154 CreateNonceAccount {
155 nonce_account: SignerIndex,
156 seed: Option<String>,
157 nonce_authority: Option<Pubkey>,
158 memo: Option<String>,
159 amount: SpendAmount,
160 compute_unit_price: Option<u64>,
161 },
162 GetNonce(Pubkey),
163 NewNonce {
164 nonce_account: Pubkey,
165 nonce_authority: SignerIndex,
166 memo: Option<String>,
167 compute_unit_price: Option<u64>,
168 },
169 ShowNonceAccount {
170 nonce_account_pubkey: Pubkey,
171 use_lamports_unit: bool,
172 },
173 WithdrawFromNonceAccount {
174 nonce_account: Pubkey,
175 nonce_authority: SignerIndex,
176 memo: Option<String>,
177 destination_account_pubkey: Pubkey,
178 lamports: u64,
179 compute_unit_price: Option<u64>,
180 },
181 UpgradeNonceAccount {
182 nonce_account: Pubkey,
183 memo: Option<String>,
184 compute_unit_price: Option<u64>,
185 },
186 Deploy,
188 Program(ProgramCliCommand),
189 ProgramV4(ProgramV4CliCommand),
190 CreateStakeAccount {
192 stake_account: SignerIndex,
193 seed: Option<String>,
194 staker: Option<Pubkey>,
195 withdrawer: Option<Pubkey>,
196 withdrawer_signer: Option<SignerIndex>,
197 lockup: Lockup,
198 amount: SpendAmount,
199 sign_only: bool,
200 dump_transaction_message: bool,
201 blockhash_query: BlockhashQuery,
202 nonce_account: Option<Pubkey>,
203 nonce_authority: SignerIndex,
204 memo: Option<String>,
205 fee_payer: SignerIndex,
206 from: SignerIndex,
207 compute_unit_price: Option<u64>,
208 },
209 DeactivateStake {
210 stake_account_pubkey: Pubkey,
211 stake_authority: SignerIndex,
212 sign_only: bool,
213 deactivate_delinquent: bool,
214 dump_transaction_message: bool,
215 blockhash_query: BlockhashQuery,
216 nonce_account: Option<Pubkey>,
217 nonce_authority: SignerIndex,
218 memo: Option<String>,
219 seed: Option<String>,
220 fee_payer: SignerIndex,
221 compute_unit_price: Option<u64>,
222 },
223 DelegateStake {
224 stake_account_pubkey: Pubkey,
225 vote_account_pubkey: Pubkey,
226 stake_authority: SignerIndex,
227 force: bool,
228 sign_only: bool,
229 dump_transaction_message: bool,
230 blockhash_query: BlockhashQuery,
231 nonce_account: Option<Pubkey>,
232 nonce_authority: SignerIndex,
233 memo: Option<String>,
234 fee_payer: SignerIndex,
235 compute_unit_price: Option<u64>,
236 },
237 SplitStake {
238 stake_account_pubkey: Pubkey,
239 stake_authority: SignerIndex,
240 sign_only: bool,
241 dump_transaction_message: bool,
242 blockhash_query: BlockhashQuery,
243 nonce_account: Option<Pubkey>,
244 nonce_authority: SignerIndex,
245 memo: Option<String>,
246 split_stake_account: SignerIndex,
247 seed: Option<String>,
248 lamports: u64,
249 fee_payer: SignerIndex,
250 compute_unit_price: Option<u64>,
251 rent_exempt_reserve: Option<u64>,
252 },
253 MergeStake {
254 stake_account_pubkey: Pubkey,
255 source_stake_account_pubkey: Pubkey,
256 stake_authority: SignerIndex,
257 sign_only: bool,
258 dump_transaction_message: bool,
259 blockhash_query: BlockhashQuery,
260 nonce_account: Option<Pubkey>,
261 nonce_authority: SignerIndex,
262 memo: Option<String>,
263 fee_payer: SignerIndex,
264 compute_unit_price: Option<u64>,
265 },
266 ShowStakeHistory {
267 use_lamports_unit: bool,
268 limit_results: usize,
269 },
270 ShowStakeAccount {
271 pubkey: Pubkey,
272 use_lamports_unit: bool,
273 with_rewards: Option<usize>,
274 use_csv: bool,
275 starting_epoch: Option<u64>,
276 },
277 StakeAuthorize {
278 stake_account_pubkey: Pubkey,
279 new_authorizations: Vec<StakeAuthorizationIndexed>,
280 sign_only: bool,
281 dump_transaction_message: bool,
282 blockhash_query: BlockhashQuery,
283 nonce_account: Option<Pubkey>,
284 nonce_authority: SignerIndex,
285 memo: Option<String>,
286 fee_payer: SignerIndex,
287 custodian: Option<SignerIndex>,
288 no_wait: bool,
289 compute_unit_price: Option<u64>,
290 },
291 StakeSetLockup {
292 stake_account_pubkey: Pubkey,
293 lockup: LockupArgs,
294 custodian: SignerIndex,
295 new_custodian_signer: Option<SignerIndex>,
296 sign_only: bool,
297 dump_transaction_message: bool,
298 blockhash_query: BlockhashQuery,
299 nonce_account: Option<Pubkey>,
300 nonce_authority: SignerIndex,
301 memo: Option<String>,
302 fee_payer: SignerIndex,
303 compute_unit_price: Option<u64>,
304 },
305 WithdrawStake {
306 stake_account_pubkey: Pubkey,
307 destination_account_pubkey: Pubkey,
308 amount: SpendAmount,
309 withdraw_authority: SignerIndex,
310 custodian: Option<SignerIndex>,
311 sign_only: bool,
312 dump_transaction_message: bool,
313 blockhash_query: BlockhashQuery,
314 nonce_account: Option<Pubkey>,
315 nonce_authority: SignerIndex,
316 memo: Option<String>,
317 seed: Option<String>,
318 fee_payer: SignerIndex,
319 compute_unit_price: Option<u64>,
320 },
321 GetValidatorInfo(Option<Pubkey>),
323 SetValidatorInfo {
324 validator_info: Value,
325 force_keybase: bool,
326 info_pubkey: Option<Pubkey>,
327 compute_unit_price: Option<u64>,
328 },
329 CreateVoteAccount {
331 vote_account: SignerIndex,
332 seed: Option<String>,
333 identity_account: SignerIndex,
334 authorized_voter: Option<Pubkey>,
335 authorized_withdrawer: Pubkey,
336 commission: u8,
337 sign_only: bool,
338 dump_transaction_message: bool,
339 blockhash_query: BlockhashQuery,
340 nonce_account: Option<Pubkey>,
341 nonce_authority: SignerIndex,
342 memo: Option<String>,
343 fee_payer: SignerIndex,
344 compute_unit_price: Option<u64>,
345 },
346 ShowVoteAccount {
347 pubkey: Pubkey,
348 use_lamports_unit: bool,
349 use_csv: bool,
350 with_rewards: Option<usize>,
351 starting_epoch: Option<u64>,
352 },
353 WithdrawFromVoteAccount {
354 vote_account_pubkey: Pubkey,
355 destination_account_pubkey: Pubkey,
356 withdraw_authority: SignerIndex,
357 withdraw_amount: SpendAmount,
358 sign_only: bool,
359 dump_transaction_message: bool,
360 blockhash_query: BlockhashQuery,
361 nonce_account: Option<Pubkey>,
362 nonce_authority: SignerIndex,
363 memo: Option<String>,
364 fee_payer: SignerIndex,
365 compute_unit_price: Option<u64>,
366 },
367 CloseVoteAccount {
368 vote_account_pubkey: Pubkey,
369 destination_account_pubkey: Pubkey,
370 withdraw_authority: SignerIndex,
371 memo: Option<String>,
372 fee_payer: SignerIndex,
373 compute_unit_price: Option<u64>,
374 },
375 VoteAuthorize {
376 vote_account_pubkey: Pubkey,
377 new_authorized_pubkey: Pubkey,
378 vote_authorize: VoteAuthorize,
379 sign_only: bool,
380 dump_transaction_message: bool,
381 blockhash_query: BlockhashQuery,
382 nonce_account: Option<Pubkey>,
383 nonce_authority: SignerIndex,
384 memo: Option<String>,
385 fee_payer: SignerIndex,
386 authorized: SignerIndex,
387 new_authorized: Option<SignerIndex>,
388 compute_unit_price: Option<u64>,
389 },
390 VoteUpdateValidator {
391 vote_account_pubkey: Pubkey,
392 new_identity_account: SignerIndex,
393 withdraw_authority: SignerIndex,
394 sign_only: bool,
395 dump_transaction_message: bool,
396 blockhash_query: BlockhashQuery,
397 nonce_account: Option<Pubkey>,
398 nonce_authority: SignerIndex,
399 memo: Option<String>,
400 fee_payer: SignerIndex,
401 compute_unit_price: Option<u64>,
402 },
403 VoteUpdateCommission {
404 vote_account_pubkey: Pubkey,
405 commission: u8,
406 withdraw_authority: SignerIndex,
407 sign_only: bool,
408 dump_transaction_message: bool,
409 blockhash_query: BlockhashQuery,
410 nonce_account: Option<Pubkey>,
411 nonce_authority: SignerIndex,
412 memo: Option<String>,
413 fee_payer: SignerIndex,
414 compute_unit_price: Option<u64>,
415 },
416 Address,
418 Airdrop {
419 pubkey: Option<Pubkey>,
420 lamports: u64,
421 },
422 Balance {
423 pubkey: Option<Pubkey>,
424 use_lamports_unit: bool,
425 },
426 Confirm(Signature),
427 CreateAddressWithSeed {
428 from_pubkey: Option<Pubkey>,
429 seed: String,
430 program_id: Pubkey,
431 },
432 DecodeTransaction(VersionedTransaction),
433 ResolveSigner(Option<String>),
434 ShowAccount {
435 pubkey: Pubkey,
436 output_file: Option<String>,
437 use_lamports_unit: bool,
438 },
439 Transfer {
440 amount: SpendAmount,
441 to: Pubkey,
442 from: SignerIndex,
443 sign_only: bool,
444 dump_transaction_message: bool,
445 allow_unfunded_recipient: bool,
446 no_wait: bool,
447 blockhash_query: BlockhashQuery,
448 nonce_account: Option<Pubkey>,
449 nonce_authority: SignerIndex,
450 memo: Option<String>,
451 fee_payer: SignerIndex,
452 derived_address_seed: Option<String>,
453 derived_address_program_id: Option<Pubkey>,
454 compute_unit_price: Option<u64>,
455 },
456 StakeMinimumDelegation {
457 use_lamports_unit: bool,
458 },
459 AddressLookupTable(AddressLookupTableCliCommand),
461 SignOffchainMessage {
462 message: OffchainMessage,
463 },
464 VerifyOffchainSignature {
465 signer_pubkey: Option<Pubkey>,
466 signature: Signature,
467 message: OffchainMessage,
468 },
469}
470
471#[derive(Debug, PartialEq)]
472pub struct CliCommandInfo {
473 pub command: CliCommand,
474 pub signers: CliSigners,
475}
476
477impl CliCommandInfo {
478 pub fn without_signers(command: CliCommand) -> Self {
479 Self {
480 command,
481 signers: vec![],
482 }
483 }
484}
485
486#[derive(Debug, Error)]
487pub enum CliError {
488 #[error("Bad parameter: {0}")]
489 BadParameter(String),
490 #[error(transparent)]
491 ClientError(#[from] ClientError),
492 #[error("Command not recognized: {0}")]
493 CommandNotRecognized(String),
494 #[error("Account {1} has insufficient funds for fee ({0} SOL)")]
495 InsufficientFundsForFee(String, Pubkey),
496 #[error("Account {1} has insufficient funds for spend ({0} SOL)")]
497 InsufficientFundsForSpend(String, Pubkey),
498 #[error("Account {2} has insufficient funds for spend ({0} SOL) + fee ({1} SOL)")]
499 InsufficientFundsForSpendAndFee(String, String, Pubkey),
500 #[error(transparent)]
501 InvalidNonce(solana_rpc_client_nonce_utils::Error),
502 #[error("Dynamic program error: {0}")]
503 DynamicProgramError(String),
504 #[error("RPC request error: {0}")]
505 RpcRequestError(String),
506 #[error("Keypair file not found: {0}")]
507 KeypairFileNotFound(String),
508 #[error("Invalid signature")]
509 InvalidSignature,
510}
511
512impl From<Box<dyn error::Error>> for CliError {
513 fn from(error: Box<dyn error::Error>) -> Self {
514 CliError::DynamicProgramError(error.to_string())
515 }
516}
517
518impl From<solana_rpc_client_nonce_utils::Error> for CliError {
519 fn from(error: solana_rpc_client_nonce_utils::Error) -> Self {
520 match error {
521 solana_rpc_client_nonce_utils::Error::Client(client_error) => {
522 Self::RpcRequestError(client_error)
523 }
524 _ => Self::InvalidNonce(error),
525 }
526 }
527}
528
529pub struct CliConfig<'a> {
530 pub command: CliCommand,
531 pub json_rpc_url: String,
532 pub websocket_url: String,
533 pub keypair_path: String,
534 pub commitment: CommitmentConfig,
535 pub signers: Vec<&'a dyn Signer>,
536 pub rpc_client: Option<Arc<RpcClient>>,
537 pub rpc_timeout: Duration,
538 pub verbose: bool,
539 pub output_format: OutputFormat,
540 pub send_transaction_config: RpcSendTransactionConfig,
541 pub confirm_transaction_initial_timeout: Duration,
542 pub address_labels: HashMap<String, String>,
543 pub use_quic: bool,
544 pub use_tpu_client: bool,
545}
546
547impl CliConfig<'_> {
548 pub(crate) fn pubkey(&self) -> Result<Pubkey, SignerError> {
549 if !self.signers.is_empty() {
550 self.signers[0].try_pubkey()
551 } else {
552 Err(SignerError::Custom(
553 "Default keypair must be set if pubkey arg not provided".to_string(),
554 ))
555 }
556 }
557
558 pub fn recent_for_tests() -> Self {
559 Self {
560 commitment: CommitmentConfig::processed(),
561 send_transaction_config: RpcSendTransactionConfig {
562 skip_preflight: true,
563 preflight_commitment: Some(CommitmentConfig::processed().commitment),
564 ..RpcSendTransactionConfig::default()
565 },
566 ..Self::default()
567 }
568 }
569}
570
571impl Default for CliConfig<'_> {
572 fn default() -> CliConfig<'static> {
573 CliConfig {
574 command: CliCommand::Balance {
575 pubkey: Some(Pubkey::default()),
576 use_lamports_unit: false,
577 },
578 json_rpc_url: ConfigInput::default().json_rpc_url,
579 websocket_url: ConfigInput::default().websocket_url,
580 keypair_path: ConfigInput::default().keypair_path,
581 commitment: ConfigInput::default().commitment,
582 signers: Vec::new(),
583 rpc_client: None,
584 rpc_timeout: Duration::from_secs(u64::from_str(DEFAULT_RPC_TIMEOUT_SECONDS).unwrap()),
585 verbose: false,
586 output_format: OutputFormat::Display,
587 send_transaction_config: RpcSendTransactionConfig::default(),
588 confirm_transaction_initial_timeout: Duration::from_secs(
589 u64::from_str(DEFAULT_CONFIRM_TX_TIMEOUT_SECONDS).unwrap(),
590 ),
591 address_labels: HashMap::new(),
592 use_quic: !DEFAULT_TPU_ENABLE_UDP,
593 use_tpu_client: DEFAULT_PING_USE_TPU_CLIENT,
594 }
595 }
596}
597
598pub fn parse_command(
599 matches: &ArgMatches<'_>,
600 default_signer: &DefaultSigner,
601 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
602) -> Result<CliCommandInfo, Box<dyn error::Error>> {
603 let response = match matches.subcommand() {
604 ("completion", Some(matches)) => {
606 let shell_choice = match matches.value_of("shell") {
607 Some("bash") => Shell::Bash,
608 Some("fish") => Shell::Fish,
609 Some("zsh") => Shell::Zsh,
610 Some("powershell") => Shell::PowerShell,
611 Some("elvish") => Shell::Elvish,
612 _ => unreachable!(),
615 };
616 get_clap_app(
617 crate_name!(),
618 crate_description!(),
619 solana_version::version!(),
620 )
621 .gen_completions_to("solana", shell_choice, &mut stdout());
622 std::process::exit(0);
623 }
624 ("block", Some(matches)) => parse_get_block(matches),
626 ("recent-prioritization-fees", Some(matches)) => {
627 parse_get_recent_prioritization_fees(matches)
628 }
629 ("block-height", Some(matches)) => parse_get_block_height(matches),
630 ("block-production", Some(matches)) => parse_show_block_production(matches),
631 ("block-time", Some(matches)) => parse_get_block_time(matches),
632 ("catchup", Some(matches)) => parse_catchup(matches, wallet_manager),
633 ("cluster-date", Some(_matches)) => {
634 Ok(CliCommandInfo::without_signers(CliCommand::ClusterDate))
635 }
636 ("cluster-version", Some(_matches)) => {
637 Ok(CliCommandInfo::without_signers(CliCommand::ClusterVersion))
638 }
639 ("epoch", Some(matches)) => parse_get_epoch(matches),
640 ("epoch-info", Some(matches)) => parse_get_epoch_info(matches),
641 ("feature", Some(matches)) => {
642 parse_feature_subcommand(matches, default_signer, wallet_manager)
643 }
644 ("first-available-block", Some(_matches)) => Ok(CliCommandInfo::without_signers(
645 CliCommand::FirstAvailableBlock,
646 )),
647 ("genesis-hash", Some(_matches)) => {
648 Ok(CliCommandInfo::without_signers(CliCommand::GetGenesisHash))
649 }
650 ("gossip", Some(_matches)) => Ok(CliCommandInfo::without_signers(CliCommand::ShowGossip)),
651 ("inflation", Some(matches)) => {
652 parse_inflation_subcommand(matches, default_signer, wallet_manager)
653 }
654 ("largest-accounts", Some(matches)) => parse_largest_accounts(matches),
655 ("leader-schedule", Some(matches)) => parse_leader_schedule(matches),
656 ("live-slots", Some(_matches)) => {
657 Ok(CliCommandInfo::without_signers(CliCommand::LiveSlots))
658 }
659 ("logs", Some(matches)) => parse_logs(matches, wallet_manager),
660 ("ping", Some(matches)) => parse_cluster_ping(matches, default_signer, wallet_manager),
661 ("rent", Some(matches)) => {
662 let data_length = value_of::<RentLengthValue>(matches, "data_length")
663 .unwrap()
664 .length();
665 let use_lamports_unit = matches.is_present("lamports");
666 Ok(CliCommandInfo::without_signers(CliCommand::Rent {
667 data_length,
668 use_lamports_unit,
669 }))
670 }
671 ("slot", Some(matches)) => parse_get_slot(matches),
672 ("stakes", Some(matches)) => parse_show_stakes(matches, wallet_manager),
673 ("supply", Some(matches)) => parse_supply(matches),
674 ("total-supply", Some(matches)) => parse_total_supply(matches),
675 ("transaction-count", Some(matches)) => parse_get_transaction_count(matches),
676 ("transaction-history", Some(matches)) => {
677 parse_transaction_history(matches, wallet_manager)
678 }
679 ("validators", Some(matches)) => parse_show_validators(matches),
680 ("authorize-nonce-account", Some(matches)) => {
682 parse_authorize_nonce_account(matches, default_signer, wallet_manager)
683 }
684 ("create-nonce-account", Some(matches)) => {
685 parse_nonce_create_account(matches, default_signer, wallet_manager)
686 }
687 ("nonce", Some(matches)) => parse_get_nonce(matches, wallet_manager),
688 ("new-nonce", Some(matches)) => parse_new_nonce(matches, default_signer, wallet_manager),
689 ("nonce-account", Some(matches)) => parse_show_nonce_account(matches, wallet_manager),
690 ("withdraw-from-nonce-account", Some(matches)) => {
691 parse_withdraw_from_nonce_account(matches, default_signer, wallet_manager)
692 }
693 ("upgrade-nonce-account", Some(matches)) => parse_upgrade_nonce_account(matches),
694 ("deploy", Some(_matches)) => clap::Error::with_description(
696 "`solana deploy` has been replaced with `solana program deploy`",
697 clap::ErrorKind::UnrecognizedSubcommand,
698 )
699 .exit(),
700 ("program", Some(matches)) => {
701 parse_program_subcommand(matches, default_signer, wallet_manager)
702 }
703 ("program-v4", Some(matches)) => {
704 parse_program_v4_subcommand(matches, default_signer, wallet_manager)
705 }
706 ("address-lookup-table", Some(matches)) => {
707 parse_address_lookup_table_subcommand(matches, default_signer, wallet_manager)
708 }
709 ("wait-for-max-stake", Some(matches)) => {
710 let max_stake_percent = value_t_or_exit!(matches, "max_percent", f32);
711 Ok(CliCommandInfo::without_signers(
712 CliCommand::WaitForMaxStake { max_stake_percent },
713 ))
714 }
715 ("create-stake-account", Some(matches)) => {
717 parse_create_stake_account(matches, default_signer, wallet_manager, !CHECKED)
718 }
719 ("create-stake-account-checked", Some(matches)) => {
720 parse_create_stake_account(matches, default_signer, wallet_manager, CHECKED)
721 }
722 ("delegate-stake", Some(matches)) => {
723 parse_stake_delegate_stake(matches, default_signer, wallet_manager)
724 }
725 ("redelegate-stake", _) => Err(CliError::CommandNotRecognized(
726 "`redelegate-stake` no longer exists and will be completely removed in a future \
727 release"
728 .to_string(),
729 )),
730 ("withdraw-stake", Some(matches)) => {
731 parse_stake_withdraw_stake(matches, default_signer, wallet_manager)
732 }
733 ("deactivate-stake", Some(matches)) => {
734 parse_stake_deactivate_stake(matches, default_signer, wallet_manager)
735 }
736 ("split-stake", Some(matches)) => {
737 parse_split_stake(matches, default_signer, wallet_manager)
738 }
739 ("merge-stake", Some(matches)) => {
740 parse_merge_stake(matches, default_signer, wallet_manager)
741 }
742 ("stake-authorize", Some(matches)) => {
743 parse_stake_authorize(matches, default_signer, wallet_manager, !CHECKED)
744 }
745 ("stake-authorize-checked", Some(matches)) => {
746 parse_stake_authorize(matches, default_signer, wallet_manager, CHECKED)
747 }
748 ("stake-set-lockup", Some(matches)) => {
749 parse_stake_set_lockup(matches, default_signer, wallet_manager, !CHECKED)
750 }
751 ("stake-set-lockup-checked", Some(matches)) => {
752 parse_stake_set_lockup(matches, default_signer, wallet_manager, CHECKED)
753 }
754 ("stake-account", Some(matches)) => parse_show_stake_account(matches, wallet_manager),
755 ("stake-history", Some(matches)) => parse_show_stake_history(matches),
756 ("stake-minimum-delegation", Some(matches)) => parse_stake_minimum_delegation(matches),
757 ("validator-info", Some(matches)) => match matches.subcommand() {
759 ("publish", Some(matches)) => {
760 parse_validator_info_command(matches, default_signer, wallet_manager)
761 }
762 ("get", Some(matches)) => parse_get_validator_info_command(matches),
763 _ => unreachable!(),
764 },
765 ("create-vote-account", Some(matches)) => {
767 parse_create_vote_account(matches, default_signer, wallet_manager)
768 }
769 ("vote-update-validator", Some(matches)) => {
770 parse_vote_update_validator(matches, default_signer, wallet_manager)
771 }
772 ("vote-update-commission", Some(matches)) => {
773 parse_vote_update_commission(matches, default_signer, wallet_manager)
774 }
775 ("vote-authorize-voter", Some(matches)) => parse_vote_authorize(
776 matches,
777 default_signer,
778 wallet_manager,
779 VoteAuthorize::Voter,
780 !CHECKED,
781 ),
782 ("vote-authorize-withdrawer", Some(matches)) => parse_vote_authorize(
783 matches,
784 default_signer,
785 wallet_manager,
786 VoteAuthorize::Withdrawer,
787 !CHECKED,
788 ),
789 ("vote-authorize-voter-checked", Some(matches)) => parse_vote_authorize(
790 matches,
791 default_signer,
792 wallet_manager,
793 VoteAuthorize::Voter,
794 CHECKED,
795 ),
796 ("vote-authorize-withdrawer-checked", Some(matches)) => parse_vote_authorize(
797 matches,
798 default_signer,
799 wallet_manager,
800 VoteAuthorize::Withdrawer,
801 CHECKED,
802 ),
803 ("vote-account", Some(matches)) => parse_vote_get_account_command(matches, wallet_manager),
804 ("withdraw-from-vote-account", Some(matches)) => {
805 parse_withdraw_from_vote_account(matches, default_signer, wallet_manager)
806 }
807 ("close-vote-account", Some(matches)) => {
808 parse_close_vote_account(matches, default_signer, wallet_manager)
809 }
810 ("account", Some(matches)) => parse_account(matches, wallet_manager),
812 ("address", Some(matches)) => Ok(CliCommandInfo {
813 command: CliCommand::Address,
814 signers: vec![default_signer.signer_from_path(matches, wallet_manager)?],
815 }),
816 ("airdrop", Some(matches)) => parse_airdrop(matches, default_signer, wallet_manager),
817 ("balance", Some(matches)) => parse_balance(matches, default_signer, wallet_manager),
818 ("confirm", Some(matches)) => match matches.value_of("signature").unwrap().parse() {
819 Ok(signature) => Ok(CliCommandInfo::without_signers(CliCommand::Confirm(
820 signature,
821 ))),
822 _ => Err(CliError::BadParameter("Invalid signature".to_string())),
823 },
824 ("create-address-with-seed", Some(matches)) => {
825 parse_create_address_with_seed(matches, default_signer, wallet_manager)
826 }
827 ("find-program-derived-address", Some(matches)) => {
828 parse_find_program_derived_address(matches)
829 }
830 ("decode-transaction", Some(matches)) => parse_decode_transaction(matches),
831 ("resolve-signer", Some(matches)) => {
832 let signer_path = resolve_signer(matches, "signer", wallet_manager)?;
833 Ok(CliCommandInfo::without_signers(CliCommand::ResolveSigner(
834 signer_path,
835 )))
836 }
837 ("transfer", Some(matches)) => parse_transfer(matches, default_signer, wallet_manager),
838 ("sign-offchain-message", Some(matches)) => {
839 parse_sign_offchain_message(matches, default_signer, wallet_manager)
840 }
841 ("verify-offchain-signature", Some(matches)) => {
842 parse_verify_offchain_signature(matches, default_signer, wallet_manager)
843 }
844 ("", None) => {
846 eprintln!("{}", matches.usage());
847 Err(CliError::CommandNotRecognized(
848 "no subcommand given".to_string(),
849 ))
850 }
851 _ => unreachable!(),
852 }?;
853 Ok(response)
854}
855
856pub type ProcessResult = Result<String, Box<dyn std::error::Error>>;
857
858pub fn process_command(config: &CliConfig) -> ProcessResult {
859 if config.verbose && config.output_format == OutputFormat::DisplayVerbose {
860 println_name_value("RPC URL:", &config.json_rpc_url);
861 println_name_value("Default Signer Path:", &config.keypair_path);
862 if config.keypair_path.starts_with("usb://") {
863 let pubkey = config
864 .pubkey()
865 .map(|pubkey| format!("{pubkey:?}"))
866 .unwrap_or_else(|_| "Unavailable".to_string());
867 println_name_value("Pubkey:", &pubkey);
868 }
869 println_name_value("Commitment:", &config.commitment.commitment.to_string());
870 }
871
872 let rpc_client = if config.rpc_client.is_none() {
873 Arc::new(RpcClient::new_with_timeouts_and_commitment(
874 config.json_rpc_url.to_string(),
875 config.rpc_timeout,
876 config.commitment,
877 config.confirm_transaction_initial_timeout,
878 ))
879 } else {
880 config.rpc_client.as_ref().unwrap().clone()
882 };
883
884 match &config.command {
885 CliCommand::Address => Ok(format!("{}", config.pubkey()?)),
888 CliCommand::Catchup {
890 node_pubkey,
891 node_json_rpc_url,
892 follow,
893 our_localhost_port,
894 log,
895 } => process_catchup(
896 &rpc_client.clone(),
897 config,
898 *node_pubkey,
899 node_json_rpc_url.clone(),
900 *follow,
901 *our_localhost_port,
902 *log,
903 ),
904 CliCommand::ClusterDate => process_cluster_date(&rpc_client, config),
905 CliCommand::ClusterVersion => process_cluster_version(&rpc_client, config),
906 CliCommand::CreateAddressWithSeed {
907 from_pubkey,
908 seed,
909 program_id,
910 } => process_create_address_with_seed(config, from_pubkey.as_ref(), seed, program_id),
911 CliCommand::Feature(feature_subcommand) => {
912 process_feature_subcommand(&rpc_client, config, feature_subcommand)
913 }
914 CliCommand::FindProgramDerivedAddress { seeds, program_id } => {
915 process_find_program_derived_address(config, seeds, program_id)
916 }
917 CliCommand::FirstAvailableBlock => process_first_available_block(&rpc_client),
918 CliCommand::GetBlock { slot } => process_get_block(&rpc_client, config, *slot),
919 CliCommand::GetBlockTime { slot } => process_get_block_time(&rpc_client, config, *slot),
920 CliCommand::GetRecentPrioritizationFees {
921 accounts,
922 limit_num_slots,
923 } => process_get_recent_priority_fees(&rpc_client, config, accounts, *limit_num_slots),
924 CliCommand::GetEpoch => process_get_epoch(&rpc_client, config),
925 CliCommand::GetEpochInfo => process_get_epoch_info(&rpc_client, config),
926 CliCommand::GetGenesisHash => process_get_genesis_hash(&rpc_client),
927 CliCommand::GetSlot => process_get_slot(&rpc_client, config),
928 CliCommand::GetBlockHeight => process_get_block_height(&rpc_client, config),
929 CliCommand::LargestAccounts { filter } => {
930 process_largest_accounts(&rpc_client, config, filter.clone())
931 }
932 CliCommand::GetTransactionCount => process_get_transaction_count(&rpc_client, config),
933 CliCommand::Inflation(inflation_subcommand) => {
934 process_inflation_subcommand(&rpc_client, config, inflation_subcommand)
935 }
936 CliCommand::LeaderSchedule { epoch } => {
937 process_leader_schedule(&rpc_client, config, *epoch)
938 }
939 CliCommand::LiveSlots => process_live_slots(config),
940 CliCommand::Logs { filter } => process_logs(config, filter),
941 CliCommand::Ping {
942 interval,
943 count,
944 timeout,
945 blockhash,
946 print_timestamp,
947 compute_unit_price,
948 } => {
949 let client_dyn: Arc<dyn TpsClient + 'static> = if config.use_tpu_client {
950 let keypair = read_keypair_file(&config.keypair_path).unwrap_or(Keypair::new());
951 #[cfg(feature = "dev-context-only-utils")]
952 let connection_cache = create_connection_cache_for_tests(
953 DEFAULT_TPU_CONNECTION_POOL_SIZE,
954 config.use_quic,
955 "127.0.0.1".parse().unwrap(),
956 Some(&keypair),
957 rpc_client.clone(),
958 );
959 #[cfg(not(feature = "dev-context-only-utils"))]
960 let connection_cache = create_connection_cache(
961 DEFAULT_TPU_CONNECTION_POOL_SIZE,
962 config.use_quic,
963 "127.0.0.1".parse().unwrap(),
964 Some(&keypair),
965 rpc_client.clone(),
966 );
967 match connection_cache {
968 ConnectionCache::Udp(cache) => Arc::new(
969 TpuClient::new_with_connection_cache(
970 rpc_client.clone(),
971 &config.websocket_url,
972 TpuClientConfig::default(),
973 cache,
974 )
975 .unwrap_or_else(|err| {
976 eprintln!("Could not create TpuClient {err:?}");
977 exit(1);
978 }),
979 ),
980 ConnectionCache::Quic(cache) => Arc::new(
981 TpuClient::new_with_connection_cache(
982 rpc_client.clone(),
983 &config.websocket_url,
984 TpuClientConfig::default(),
985 cache,
986 )
987 .unwrap_or_else(|err| {
988 eprintln!("Could not create TpuClient {err:?}");
989 exit(1);
990 }),
991 ),
992 }
993 } else {
994 rpc_client.clone() as Arc<dyn TpsClient + 'static>
995 };
996 process_ping(
997 &client_dyn,
998 config,
999 interval,
1000 count,
1001 timeout,
1002 blockhash,
1003 *print_timestamp,
1004 *compute_unit_price,
1005 &rpc_client,
1006 )
1007 }
1008 CliCommand::Rent {
1009 data_length,
1010 use_lamports_unit,
1011 } => process_calculate_rent(&rpc_client, config, *data_length, *use_lamports_unit),
1012 CliCommand::ShowBlockProduction { epoch, slot_limit } => {
1013 process_show_block_production(&rpc_client, config, *epoch, *slot_limit)
1014 }
1015 CliCommand::ShowGossip => process_show_gossip(&rpc_client, config),
1016 CliCommand::ShowStakes {
1017 use_lamports_unit,
1018 vote_account_pubkeys,
1019 withdraw_authority,
1020 } => process_show_stakes(
1021 &rpc_client,
1022 config,
1023 *use_lamports_unit,
1024 vote_account_pubkeys.as_deref(),
1025 withdraw_authority.as_ref(),
1026 ),
1027 CliCommand::WaitForMaxStake { max_stake_percent } => {
1028 process_wait_for_max_stake(&rpc_client, config, *max_stake_percent)
1029 }
1030 CliCommand::ShowValidators {
1031 use_lamports_unit,
1032 sort_order,
1033 reverse_sort,
1034 number_validators,
1035 keep_unstaked_delinquents,
1036 delinquent_slot_distance,
1037 } => process_show_validators(
1038 &rpc_client,
1039 config,
1040 *use_lamports_unit,
1041 *sort_order,
1042 *reverse_sort,
1043 *number_validators,
1044 *keep_unstaked_delinquents,
1045 *delinquent_slot_distance,
1046 ),
1047 CliCommand::Supply { print_accounts } => {
1048 process_supply(&rpc_client, config, *print_accounts)
1049 }
1050 CliCommand::TotalSupply => process_total_supply(&rpc_client, config),
1051 CliCommand::TransactionHistory {
1052 address,
1053 before,
1054 until,
1055 limit,
1056 show_transactions,
1057 } => process_transaction_history(
1058 &rpc_client,
1059 config,
1060 address,
1061 *before,
1062 *until,
1063 *limit,
1064 *show_transactions,
1065 ),
1066
1067 CliCommand::AuthorizeNonceAccount {
1071 nonce_account,
1072 nonce_authority,
1073 memo,
1074 new_authority,
1075 compute_unit_price,
1076 } => process_authorize_nonce_account(
1077 &rpc_client,
1078 config,
1079 nonce_account,
1080 *nonce_authority,
1081 memo.as_ref(),
1082 new_authority,
1083 *compute_unit_price,
1084 ),
1085 CliCommand::CreateNonceAccount {
1087 nonce_account,
1088 seed,
1089 nonce_authority,
1090 memo,
1091 amount,
1092 compute_unit_price,
1093 } => process_create_nonce_account(
1094 &rpc_client,
1095 config,
1096 *nonce_account,
1097 seed.clone(),
1098 *nonce_authority,
1099 memo.as_ref(),
1100 *amount,
1101 *compute_unit_price,
1102 ),
1103 CliCommand::GetNonce(nonce_account_pubkey) => {
1105 process_get_nonce(&rpc_client, config, nonce_account_pubkey)
1106 }
1107 CliCommand::NewNonce {
1109 nonce_account,
1110 nonce_authority,
1111 memo,
1112 compute_unit_price,
1113 } => process_new_nonce(
1114 &rpc_client,
1115 config,
1116 nonce_account,
1117 *nonce_authority,
1118 memo.as_ref(),
1119 *compute_unit_price,
1120 ),
1121 CliCommand::ShowNonceAccount {
1123 nonce_account_pubkey,
1124 use_lamports_unit,
1125 } => process_show_nonce_account(
1126 &rpc_client,
1127 config,
1128 nonce_account_pubkey,
1129 *use_lamports_unit,
1130 ),
1131 CliCommand::WithdrawFromNonceAccount {
1133 nonce_account,
1134 nonce_authority,
1135 memo,
1136 destination_account_pubkey,
1137 lamports,
1138 compute_unit_price,
1139 } => process_withdraw_from_nonce_account(
1140 &rpc_client,
1141 config,
1142 nonce_account,
1143 *nonce_authority,
1144 memo.as_ref(),
1145 destination_account_pubkey,
1146 *lamports,
1147 *compute_unit_price,
1148 ),
1149 CliCommand::UpgradeNonceAccount {
1151 nonce_account,
1152 memo,
1153 compute_unit_price,
1154 } => process_upgrade_nonce_account(
1155 &rpc_client,
1156 config,
1157 *nonce_account,
1158 memo.as_ref(),
1159 *compute_unit_price,
1160 ),
1161
1162 CliCommand::Deploy => {
1164 std::process::exit(1);
1167 }
1168
1169 CliCommand::Program(program_subcommand) => {
1171 process_program_subcommand(rpc_client, config, program_subcommand)
1172 }
1173
1174 CliCommand::ProgramV4(program_subcommand) => {
1176 process_program_v4_subcommand(rpc_client, config, program_subcommand)
1177 }
1178
1179 CliCommand::CreateStakeAccount {
1183 stake_account,
1184 seed,
1185 staker,
1186 withdrawer,
1187 withdrawer_signer,
1188 lockup,
1189 amount,
1190 sign_only,
1191 dump_transaction_message,
1192 blockhash_query,
1193 ref nonce_account,
1194 nonce_authority,
1195 memo,
1196 fee_payer,
1197 from,
1198 compute_unit_price,
1199 } => process_create_stake_account(
1200 &rpc_client,
1201 config,
1202 *stake_account,
1203 seed,
1204 staker,
1205 withdrawer,
1206 *withdrawer_signer,
1207 lockup,
1208 *amount,
1209 *sign_only,
1210 *dump_transaction_message,
1211 blockhash_query,
1212 nonce_account.as_ref(),
1213 *nonce_authority,
1214 memo.as_ref(),
1215 *fee_payer,
1216 *from,
1217 *compute_unit_price,
1218 ),
1219 CliCommand::DeactivateStake {
1220 stake_account_pubkey,
1221 stake_authority,
1222 sign_only,
1223 deactivate_delinquent,
1224 dump_transaction_message,
1225 blockhash_query,
1226 nonce_account,
1227 nonce_authority,
1228 memo,
1229 seed,
1230 fee_payer,
1231 compute_unit_price,
1232 } => process_deactivate_stake_account(
1233 &rpc_client,
1234 config,
1235 stake_account_pubkey,
1236 *stake_authority,
1237 *sign_only,
1238 *deactivate_delinquent,
1239 *dump_transaction_message,
1240 blockhash_query,
1241 *nonce_account,
1242 *nonce_authority,
1243 memo.as_ref(),
1244 seed.as_ref(),
1245 *fee_payer,
1246 *compute_unit_price,
1247 ),
1248 CliCommand::DelegateStake {
1249 stake_account_pubkey,
1250 vote_account_pubkey,
1251 stake_authority,
1252 force,
1253 sign_only,
1254 dump_transaction_message,
1255 blockhash_query,
1256 nonce_account,
1257 nonce_authority,
1258 memo,
1259 fee_payer,
1260 compute_unit_price,
1261 } => process_delegate_stake(
1262 &rpc_client,
1263 config,
1264 stake_account_pubkey,
1265 vote_account_pubkey,
1266 *stake_authority,
1267 *force,
1268 *sign_only,
1269 *dump_transaction_message,
1270 blockhash_query,
1271 *nonce_account,
1272 *nonce_authority,
1273 memo.as_ref(),
1274 *fee_payer,
1275 *compute_unit_price,
1276 ),
1277 CliCommand::SplitStake {
1278 stake_account_pubkey,
1279 stake_authority,
1280 sign_only,
1281 dump_transaction_message,
1282 blockhash_query,
1283 nonce_account,
1284 nonce_authority,
1285 memo,
1286 split_stake_account,
1287 seed,
1288 lamports,
1289 fee_payer,
1290 compute_unit_price,
1291 rent_exempt_reserve,
1292 } => process_split_stake(
1293 &rpc_client,
1294 config,
1295 stake_account_pubkey,
1296 *stake_authority,
1297 *sign_only,
1298 *dump_transaction_message,
1299 blockhash_query,
1300 *nonce_account,
1301 *nonce_authority,
1302 memo.as_ref(),
1303 *split_stake_account,
1304 seed,
1305 *lamports,
1306 *fee_payer,
1307 *compute_unit_price,
1308 rent_exempt_reserve.as_ref(),
1309 ),
1310 CliCommand::MergeStake {
1311 stake_account_pubkey,
1312 source_stake_account_pubkey,
1313 stake_authority,
1314 sign_only,
1315 dump_transaction_message,
1316 blockhash_query,
1317 nonce_account,
1318 nonce_authority,
1319 memo,
1320 fee_payer,
1321 compute_unit_price,
1322 } => process_merge_stake(
1323 &rpc_client,
1324 config,
1325 stake_account_pubkey,
1326 source_stake_account_pubkey,
1327 *stake_authority,
1328 *sign_only,
1329 *dump_transaction_message,
1330 blockhash_query,
1331 *nonce_account,
1332 *nonce_authority,
1333 memo.as_ref(),
1334 *fee_payer,
1335 *compute_unit_price,
1336 ),
1337 CliCommand::ShowStakeAccount {
1338 pubkey: stake_account_pubkey,
1339 use_lamports_unit,
1340 with_rewards,
1341 use_csv,
1342 starting_epoch,
1343 } => process_show_stake_account(
1344 &rpc_client,
1345 config,
1346 stake_account_pubkey,
1347 *use_lamports_unit,
1348 *with_rewards,
1349 *use_csv,
1350 *starting_epoch,
1351 ),
1352 CliCommand::ShowStakeHistory {
1353 use_lamports_unit,
1354 limit_results,
1355 } => process_show_stake_history(&rpc_client, config, *use_lamports_unit, *limit_results),
1356 CliCommand::StakeAuthorize {
1357 stake_account_pubkey,
1358 ref new_authorizations,
1359 sign_only,
1360 dump_transaction_message,
1361 blockhash_query,
1362 nonce_account,
1363 nonce_authority,
1364 memo,
1365 fee_payer,
1366 custodian,
1367 no_wait,
1368 compute_unit_price,
1369 } => process_stake_authorize(
1370 &rpc_client,
1371 config,
1372 stake_account_pubkey,
1373 new_authorizations,
1374 *custodian,
1375 *sign_only,
1376 *dump_transaction_message,
1377 blockhash_query,
1378 *nonce_account,
1379 *nonce_authority,
1380 memo.as_ref(),
1381 *fee_payer,
1382 *no_wait,
1383 *compute_unit_price,
1384 ),
1385 CliCommand::StakeSetLockup {
1386 stake_account_pubkey,
1387 lockup,
1388 custodian,
1389 new_custodian_signer,
1390 sign_only,
1391 dump_transaction_message,
1392 blockhash_query,
1393 nonce_account,
1394 nonce_authority,
1395 memo,
1396 fee_payer,
1397 compute_unit_price,
1398 } => process_stake_set_lockup(
1399 &rpc_client,
1400 config,
1401 stake_account_pubkey,
1402 lockup,
1403 *new_custodian_signer,
1404 *custodian,
1405 *sign_only,
1406 *dump_transaction_message,
1407 blockhash_query,
1408 *nonce_account,
1409 *nonce_authority,
1410 memo.as_ref(),
1411 *fee_payer,
1412 *compute_unit_price,
1413 ),
1414 CliCommand::WithdrawStake {
1415 stake_account_pubkey,
1416 destination_account_pubkey,
1417 amount,
1418 withdraw_authority,
1419 custodian,
1420 sign_only,
1421 dump_transaction_message,
1422 blockhash_query,
1423 ref nonce_account,
1424 nonce_authority,
1425 memo,
1426 seed,
1427 fee_payer,
1428 compute_unit_price,
1429 } => process_withdraw_stake(
1430 &rpc_client,
1431 config,
1432 stake_account_pubkey,
1433 destination_account_pubkey,
1434 *amount,
1435 *withdraw_authority,
1436 *custodian,
1437 *sign_only,
1438 *dump_transaction_message,
1439 blockhash_query,
1440 nonce_account.as_ref(),
1441 *nonce_authority,
1442 memo.as_ref(),
1443 seed.as_ref(),
1444 *fee_payer,
1445 *compute_unit_price,
1446 ),
1447 CliCommand::StakeMinimumDelegation { use_lamports_unit } => {
1448 process_stake_minimum_delegation(&rpc_client, config, *use_lamports_unit)
1449 }
1450
1451 CliCommand::GetValidatorInfo(info_pubkey) => {
1455 process_get_validator_info(&rpc_client, config, *info_pubkey)
1456 }
1457 CliCommand::SetValidatorInfo {
1459 validator_info,
1460 force_keybase,
1461 info_pubkey,
1462 compute_unit_price,
1463 } => process_set_validator_info(
1464 &rpc_client,
1465 config,
1466 validator_info,
1467 *force_keybase,
1468 *info_pubkey,
1469 *compute_unit_price,
1470 ),
1471
1472 CliCommand::CreateVoteAccount {
1476 vote_account,
1477 seed,
1478 identity_account,
1479 authorized_voter,
1480 authorized_withdrawer,
1481 commission,
1482 sign_only,
1483 dump_transaction_message,
1484 blockhash_query,
1485 ref nonce_account,
1486 nonce_authority,
1487 memo,
1488 fee_payer,
1489 compute_unit_price,
1490 } => process_create_vote_account(
1491 &rpc_client,
1492 config,
1493 *vote_account,
1494 seed,
1495 *identity_account,
1496 authorized_voter,
1497 *authorized_withdrawer,
1498 *commission,
1499 *sign_only,
1500 *dump_transaction_message,
1501 blockhash_query,
1502 nonce_account.as_ref(),
1503 *nonce_authority,
1504 memo.as_ref(),
1505 *fee_payer,
1506 *compute_unit_price,
1507 ),
1508 CliCommand::ShowVoteAccount {
1509 pubkey: vote_account_pubkey,
1510 use_lamports_unit,
1511 use_csv,
1512 with_rewards,
1513 starting_epoch,
1514 } => process_show_vote_account(
1515 &rpc_client,
1516 config,
1517 vote_account_pubkey,
1518 *use_lamports_unit,
1519 *use_csv,
1520 *with_rewards,
1521 *starting_epoch,
1522 ),
1523 CliCommand::WithdrawFromVoteAccount {
1524 vote_account_pubkey,
1525 withdraw_authority,
1526 withdraw_amount,
1527 destination_account_pubkey,
1528 sign_only,
1529 dump_transaction_message,
1530 blockhash_query,
1531 ref nonce_account,
1532 nonce_authority,
1533 memo,
1534 fee_payer,
1535 compute_unit_price,
1536 } => process_withdraw_from_vote_account(
1537 &rpc_client,
1538 config,
1539 vote_account_pubkey,
1540 *withdraw_authority,
1541 *withdraw_amount,
1542 destination_account_pubkey,
1543 *sign_only,
1544 *dump_transaction_message,
1545 blockhash_query,
1546 nonce_account.as_ref(),
1547 *nonce_authority,
1548 memo.as_ref(),
1549 *fee_payer,
1550 *compute_unit_price,
1551 ),
1552 CliCommand::CloseVoteAccount {
1553 vote_account_pubkey,
1554 withdraw_authority,
1555 destination_account_pubkey,
1556 memo,
1557 fee_payer,
1558 compute_unit_price,
1559 } => process_close_vote_account(
1560 &rpc_client,
1561 config,
1562 vote_account_pubkey,
1563 *withdraw_authority,
1564 destination_account_pubkey,
1565 memo.as_ref(),
1566 *fee_payer,
1567 *compute_unit_price,
1568 ),
1569 CliCommand::VoteAuthorize {
1570 vote_account_pubkey,
1571 new_authorized_pubkey,
1572 vote_authorize,
1573 sign_only,
1574 dump_transaction_message,
1575 blockhash_query,
1576 nonce_account,
1577 nonce_authority,
1578 memo,
1579 fee_payer,
1580 authorized,
1581 new_authorized,
1582 compute_unit_price,
1583 } => process_vote_authorize(
1584 &rpc_client,
1585 config,
1586 vote_account_pubkey,
1587 new_authorized_pubkey,
1588 *vote_authorize,
1589 *authorized,
1590 *new_authorized,
1591 *sign_only,
1592 *dump_transaction_message,
1593 blockhash_query,
1594 *nonce_account,
1595 *nonce_authority,
1596 memo.as_ref(),
1597 *fee_payer,
1598 *compute_unit_price,
1599 ),
1600 CliCommand::VoteUpdateValidator {
1601 vote_account_pubkey,
1602 new_identity_account,
1603 withdraw_authority,
1604 sign_only,
1605 dump_transaction_message,
1606 blockhash_query,
1607 nonce_account,
1608 nonce_authority,
1609 memo,
1610 fee_payer,
1611 compute_unit_price,
1612 } => process_vote_update_validator(
1613 &rpc_client,
1614 config,
1615 vote_account_pubkey,
1616 *new_identity_account,
1617 *withdraw_authority,
1618 *sign_only,
1619 *dump_transaction_message,
1620 blockhash_query,
1621 *nonce_account,
1622 *nonce_authority,
1623 memo.as_ref(),
1624 *fee_payer,
1625 *compute_unit_price,
1626 ),
1627 CliCommand::VoteUpdateCommission {
1628 vote_account_pubkey,
1629 commission,
1630 withdraw_authority,
1631 sign_only,
1632 dump_transaction_message,
1633 blockhash_query,
1634 nonce_account,
1635 nonce_authority,
1636 memo,
1637 fee_payer,
1638 compute_unit_price,
1639 } => process_vote_update_commission(
1640 &rpc_client,
1641 config,
1642 vote_account_pubkey,
1643 *commission,
1644 *withdraw_authority,
1645 *sign_only,
1646 *dump_transaction_message,
1647 blockhash_query,
1648 *nonce_account,
1649 *nonce_authority,
1650 memo.as_ref(),
1651 *fee_payer,
1652 *compute_unit_price,
1653 ),
1654
1655 CliCommand::Airdrop { pubkey, lamports } => {
1659 process_airdrop(&rpc_client, config, pubkey, *lamports)
1660 }
1661 CliCommand::Balance {
1663 pubkey,
1664 use_lamports_unit,
1665 } => process_balance(&rpc_client, config, pubkey, *use_lamports_unit),
1666 CliCommand::Confirm(signature) => process_confirm(&rpc_client, config, signature),
1668 CliCommand::DecodeTransaction(transaction) => {
1669 process_decode_transaction(config, transaction)
1670 }
1671 CliCommand::ResolveSigner(path) => {
1672 if let Some(path) = path {
1673 Ok(path.to_string())
1674 } else {
1675 Ok("Signer is valid".to_string())
1676 }
1677 }
1678 CliCommand::ShowAccount {
1679 pubkey,
1680 output_file,
1681 use_lamports_unit,
1682 } => process_show_account(&rpc_client, config, pubkey, output_file, *use_lamports_unit),
1683 CliCommand::Transfer {
1684 amount,
1685 to,
1686 from,
1687 sign_only,
1688 dump_transaction_message,
1689 allow_unfunded_recipient,
1690 no_wait,
1691 ref blockhash_query,
1692 ref nonce_account,
1693 nonce_authority,
1694 memo,
1695 fee_payer,
1696 derived_address_seed,
1697 ref derived_address_program_id,
1698 compute_unit_price,
1699 } => process_transfer(
1700 &rpc_client,
1701 config,
1702 *amount,
1703 to,
1704 *from,
1705 *sign_only,
1706 *dump_transaction_message,
1707 *allow_unfunded_recipient,
1708 *no_wait,
1709 blockhash_query,
1710 nonce_account.as_ref(),
1711 *nonce_authority,
1712 memo.as_ref(),
1713 *fee_payer,
1714 derived_address_seed.clone(),
1715 derived_address_program_id.as_ref(),
1716 *compute_unit_price,
1717 ),
1718 CliCommand::AddressLookupTable(subcommand) => {
1720 process_address_lookup_table_subcommand(rpc_client, config, subcommand)
1721 }
1722 CliCommand::SignOffchainMessage { message } => {
1723 process_sign_offchain_message(config, message)
1724 }
1725 CliCommand::VerifyOffchainSignature {
1726 signer_pubkey,
1727 signature,
1728 message,
1729 } => process_verify_offchain_signature(config, signer_pubkey, signature, message),
1730 }
1731}
1732
1733pub fn request_and_confirm_airdrop(
1734 rpc_client: &RpcClient,
1735 config: &CliConfig,
1736 to_pubkey: &Pubkey,
1737 lamports: u64,
1738) -> ClientResult<Signature> {
1739 let recent_blockhash = rpc_client.get_latest_blockhash()?;
1740 let signature =
1741 rpc_client.request_airdrop_with_blockhash(to_pubkey, lamports, &recent_blockhash)?;
1742 rpc_client.confirm_transaction_with_spinner(
1743 &signature,
1744 &recent_blockhash,
1745 config.commitment,
1746 )?;
1747 Ok(signature)
1748}
1749
1750pub fn common_error_adapter<E>(ix_error: &InstructionError) -> Option<E>
1751where
1752 E: 'static + std::error::Error + FromPrimitive,
1753{
1754 match ix_error {
1755 InstructionError::Custom(code) => E::from_u32(*code),
1756 _ => None,
1757 }
1758}
1759
1760pub fn to_str_error_adapter<E>(ix_error: &InstructionError) -> Option<E>
1761where
1762 E: 'static + std::error::Error + std::convert::TryFrom<u32>,
1763{
1764 match ix_error {
1765 InstructionError::Custom(code) => E::try_from(*code).ok(),
1766 _ => None,
1767 }
1768}
1769
1770pub fn log_instruction_custom_error_to_str<E>(
1771 result: ClientResult<Signature>,
1772 config: &CliConfig,
1773) -> ProcessResult
1774where
1775 E: 'static + std::error::Error + std::convert::TryFrom<u32>,
1776{
1777 log_instruction_custom_error_ex::<E, _>(result, &config.output_format, to_str_error_adapter)
1778}
1779
1780pub fn log_instruction_custom_error<E>(
1781 result: ClientResult<Signature>,
1782 config: &CliConfig,
1783) -> ProcessResult
1784where
1785 E: 'static + std::error::Error + FromPrimitive,
1786{
1787 log_instruction_custom_error_ex::<E, _>(result, &config.output_format, common_error_adapter)
1788}
1789
1790pub fn log_instruction_custom_error_ex<E, F>(
1791 result: ClientResult<Signature>,
1792 output_format: &OutputFormat,
1793 error_adapter: F,
1794) -> ProcessResult
1795where
1796 E: 'static + std::error::Error,
1797 F: Fn(&InstructionError) -> Option<E>,
1798{
1799 match result {
1800 Err(err) => {
1801 let maybe_tx_err = err.get_transaction_error();
1802 if let Some(TransactionError::InstructionError(_, ix_error)) = maybe_tx_err {
1803 if let Some(specific_error) = error_adapter(&ix_error) {
1804 return Err(specific_error.into());
1805 }
1806 }
1807 Err(err.into())
1808 }
1809 Ok(sig) => {
1810 let signature = CliSignature {
1811 signature: sig.clone().to_string(),
1812 };
1813 Ok(output_format.formatted_string(&signature))
1814 }
1815 }
1816}
1817
1818#[cfg(test)]
1819mod tests {
1820 use {
1821 super::*,
1822 serde_json::json,
1823 solana_keypair::{keypair_from_seed, read_keypair_file, write_keypair_file, Keypair},
1824 solana_presigner::Presigner,
1825 solana_pubkey::Pubkey,
1826 solana_rpc_client::mock_sender_for_cli::SIGNATURE,
1827 solana_rpc_client_api::{
1828 request::RpcRequest,
1829 response::{Response, RpcResponseContext},
1830 },
1831 solana_rpc_client_nonce_utils::blockhash_query,
1832 solana_sdk_ids::{stake, system_program},
1833 solana_transaction_error::TransactionError,
1834 solana_transaction_status::TransactionConfirmationStatus,
1835 };
1836
1837 fn make_tmp_path(name: &str) -> String {
1838 let out_dir = std::env::var("FARF_DIR").unwrap_or_else(|_| "farf".to_string());
1839 let keypair = Keypair::new();
1840
1841 let path = format!("{}/tmp/{}-{}", out_dir, name, keypair.pubkey());
1842
1843 let _ignored = std::fs::remove_dir_all(&path);
1845 let _ignored = std::fs::remove_file(&path);
1847
1848 path
1849 }
1850
1851 #[test]
1852 fn test_generate_unique_signers() {
1853 let matches = ArgMatches::default();
1854
1855 let default_keypair = Keypair::new();
1856 let default_keypair_file = make_tmp_path("keypair_file");
1857 write_keypair_file(&default_keypair, &default_keypair_file).unwrap();
1858
1859 let default_signer = DefaultSigner::new("keypair", &default_keypair_file);
1860
1861 let signer_info = default_signer
1862 .generate_unique_signers(vec![], &matches, &mut None)
1863 .unwrap();
1864 assert_eq!(signer_info.signers.len(), 0);
1865
1866 let signer_info = default_signer
1867 .generate_unique_signers(vec![None, None], &matches, &mut None)
1868 .unwrap();
1869 assert_eq!(signer_info.signers.len(), 1);
1870 assert_eq!(signer_info.index_of(None), Some(0));
1871 assert_eq!(signer_info.index_of(Some(solana_pubkey::new_rand())), None);
1872
1873 let keypair0 = keypair_from_seed(&[1u8; 32]).unwrap();
1874 let keypair0_pubkey = keypair0.pubkey();
1875 let keypair0_clone = keypair_from_seed(&[1u8; 32]).unwrap();
1876 let keypair0_clone_pubkey = keypair0.pubkey();
1877 let signers: Vec<Option<Box<dyn Signer>>> = vec![
1878 None,
1879 Some(Box::new(keypair0)),
1880 Some(Box::new(keypair0_clone)),
1881 ];
1882 let signer_info = default_signer
1883 .generate_unique_signers(signers, &matches, &mut None)
1884 .unwrap();
1885 assert_eq!(signer_info.signers.len(), 2);
1886 assert_eq!(signer_info.index_of(None), Some(0));
1887 assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(1));
1888 assert_eq!(signer_info.index_of(Some(keypair0_clone_pubkey)), Some(1));
1889
1890 let keypair0 = keypair_from_seed(&[1u8; 32]).unwrap();
1891 let keypair0_pubkey = keypair0.pubkey();
1892 let keypair0_clone = keypair_from_seed(&[1u8; 32]).unwrap();
1893 let signers: Vec<Option<Box<dyn Signer>>> =
1894 vec![Some(Box::new(keypair0)), Some(Box::new(keypair0_clone))];
1895 let signer_info = default_signer
1896 .generate_unique_signers(signers, &matches, &mut None)
1897 .unwrap();
1898 assert_eq!(signer_info.signers.len(), 1);
1899 assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(0));
1900
1901 let keypair0 = keypair_from_seed(&[2u8; 32]).unwrap();
1903 let keypair0_pubkey = keypair0.pubkey();
1904 let keypair1 = keypair_from_seed(&[3u8; 32]).unwrap();
1905 let keypair1_pubkey = keypair1.pubkey();
1906 let message = vec![0, 1, 2, 3];
1907 let presigner0 = Presigner::new(&keypair0.pubkey(), &keypair0.sign_message(&message));
1908 let presigner0_pubkey = presigner0.pubkey();
1909 let presigner1 = Presigner::new(&keypair1.pubkey(), &keypair1.sign_message(&message));
1910 let presigner1_pubkey = presigner1.pubkey();
1911 let signers: Vec<Option<Box<dyn Signer>>> = vec![
1912 Some(Box::new(keypair0)),
1913 Some(Box::new(presigner0)),
1914 Some(Box::new(presigner1)),
1915 Some(Box::new(keypair1)),
1916 ];
1917 let signer_info = default_signer
1918 .generate_unique_signers(signers, &matches, &mut None)
1919 .unwrap();
1920 assert_eq!(signer_info.signers.len(), 2);
1921 assert_eq!(signer_info.index_of(Some(keypair0_pubkey)), Some(0));
1922 assert_eq!(signer_info.index_of(Some(keypair1_pubkey)), Some(1));
1923 assert_eq!(signer_info.index_of(Some(presigner0_pubkey)), Some(0));
1924 assert_eq!(signer_info.index_of(Some(presigner1_pubkey)), Some(1));
1925 }
1926
1927 #[test]
1928 #[allow(clippy::cognitive_complexity)]
1929 fn test_cli_parse_command() {
1930 let test_commands = get_clap_app("test", "desc", "version");
1931
1932 let pubkey = solana_pubkey::new_rand();
1933 let pubkey_string = format!("{pubkey}");
1934
1935 let default_keypair = Keypair::new();
1936 let keypair_file = make_tmp_path("keypair_file");
1937 write_keypair_file(&default_keypair, &keypair_file).unwrap();
1938 let keypair = read_keypair_file(&keypair_file).unwrap();
1939 let default_signer = DefaultSigner::new("", &keypair_file);
1940 let test_airdrop =
1942 test_commands
1943 .clone()
1944 .get_matches_from(vec!["test", "airdrop", "50", &pubkey_string]);
1945 assert_eq!(
1946 parse_command(&test_airdrop, &default_signer, &mut None).unwrap(),
1947 CliCommandInfo::without_signers(CliCommand::Airdrop {
1948 pubkey: Some(pubkey),
1949 lamports: 50_000_000_000,
1950 })
1951 );
1952
1953 let test_balance = test_commands.clone().get_matches_from(vec![
1955 "test",
1956 "balance",
1957 &keypair.pubkey().to_string(),
1958 ]);
1959 assert_eq!(
1960 parse_command(&test_balance, &default_signer, &mut None).unwrap(),
1961 CliCommandInfo::without_signers(CliCommand::Balance {
1962 pubkey: Some(keypair.pubkey()),
1963 use_lamports_unit: false,
1964 })
1965 );
1966 let test_balance = test_commands.clone().get_matches_from(vec![
1967 "test",
1968 "balance",
1969 &keypair_file,
1970 "--lamports",
1971 ]);
1972 assert_eq!(
1973 parse_command(&test_balance, &default_signer, &mut None).unwrap(),
1974 CliCommandInfo::without_signers(CliCommand::Balance {
1975 pubkey: Some(keypair.pubkey()),
1976 use_lamports_unit: true,
1977 })
1978 );
1979 let test_balance =
1980 test_commands
1981 .clone()
1982 .get_matches_from(vec!["test", "balance", "--lamports"]);
1983 assert_eq!(
1984 parse_command(&test_balance, &default_signer, &mut None).unwrap(),
1985 CliCommandInfo {
1986 command: CliCommand::Balance {
1987 pubkey: None,
1988 use_lamports_unit: true,
1989 },
1990 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
1991 }
1992 );
1993
1994 let signature = Signature::from([1; 64]);
1996 let signature_string = format!("{signature:?}");
1997 let test_confirm =
1998 test_commands
1999 .clone()
2000 .get_matches_from(vec!["test", "confirm", &signature_string]);
2001 assert_eq!(
2002 parse_command(&test_confirm, &default_signer, &mut None).unwrap(),
2003 CliCommandInfo::without_signers(CliCommand::Confirm(signature))
2004 );
2005 let test_bad_signature = test_commands
2006 .clone()
2007 .get_matches_from(vec!["test", "confirm", "deadbeef"]);
2008 assert!(parse_command(&test_bad_signature, &default_signer, &mut None).is_err());
2009
2010 let from_pubkey = solana_pubkey::new_rand();
2012 let from_str = from_pubkey.to_string();
2013 for (name, program_id) in &[
2014 ("STAKE", stake::id()),
2015 ("VOTE", solana_sdk_ids::vote::id()),
2016 ("NONCE", system_program::id()),
2017 ] {
2018 let test_create_address_with_seed = test_commands.clone().get_matches_from(vec![
2019 "test",
2020 "create-address-with-seed",
2021 "seed",
2022 name,
2023 "--from",
2024 &from_str,
2025 ]);
2026 assert_eq!(
2027 parse_command(&test_create_address_with_seed, &default_signer, &mut None).unwrap(),
2028 CliCommandInfo::without_signers(CliCommand::CreateAddressWithSeed {
2029 from_pubkey: Some(from_pubkey),
2030 seed: "seed".to_string(),
2031 program_id: *program_id
2032 })
2033 );
2034 }
2035 let test_create_address_with_seed = test_commands.clone().get_matches_from(vec![
2036 "test",
2037 "create-address-with-seed",
2038 "seed",
2039 "STAKE",
2040 ]);
2041 assert_eq!(
2042 parse_command(&test_create_address_with_seed, &default_signer, &mut None).unwrap(),
2043 CliCommandInfo {
2044 command: CliCommand::CreateAddressWithSeed {
2045 from_pubkey: None,
2046 seed: "seed".to_string(),
2047 program_id: stake::id(),
2048 },
2049 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
2050 }
2051 );
2052
2053 let test_resolve_signer =
2055 test_commands
2056 .clone()
2057 .get_matches_from(vec!["test", "resolve-signer", &keypair_file]);
2058 assert_eq!(
2059 parse_command(&test_resolve_signer, &default_signer, &mut None).unwrap(),
2060 CliCommandInfo::without_signers(CliCommand::ResolveSigner(Some(keypair_file.clone())))
2061 );
2062 let test_resolve_signer =
2064 test_commands
2065 .clone()
2066 .get_matches_from(vec!["test", "resolve-signer", &pubkey_string]);
2067 assert_eq!(
2068 parse_command(&test_resolve_signer, &default_signer, &mut None).unwrap(),
2069 CliCommandInfo::without_signers(CliCommand::ResolveSigner(Some(pubkey.to_string())))
2070 );
2071
2072 let test_sign_offchain = test_commands.clone().get_matches_from(vec![
2074 "test",
2075 "sign-offchain-message",
2076 "Test Message",
2077 ]);
2078 let message = OffchainMessage::new(0, b"Test Message").unwrap();
2079 assert_eq!(
2080 parse_command(&test_sign_offchain, &default_signer, &mut None).unwrap(),
2081 CliCommandInfo {
2082 command: CliCommand::SignOffchainMessage {
2083 message: message.clone()
2084 },
2085 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
2086 }
2087 );
2088
2089 let signature = keypair.sign_message(&message.serialize().unwrap());
2091 let test_verify_offchain = test_commands.clone().get_matches_from(vec![
2092 "test",
2093 "verify-offchain-signature",
2094 "Test Message",
2095 &signature.to_string(),
2096 ]);
2097 assert_eq!(
2098 parse_command(&test_verify_offchain, &default_signer, &mut None).unwrap(),
2099 CliCommandInfo {
2100 command: CliCommand::VerifyOffchainSignature {
2101 signer_pubkey: None,
2102 signature,
2103 message
2104 },
2105 signers: vec![Box::new(read_keypair_file(&keypair_file).unwrap())],
2106 }
2107 );
2108 }
2109
2110 #[test]
2111 #[allow(clippy::cognitive_complexity)]
2112 fn test_cli_process_command() {
2113 let mut config = CliConfig {
2115 rpc_client: Some(Arc::new(RpcClient::new_mock("succeeds".to_string()))),
2116 json_rpc_url: "http://127.0.0.1:8899".to_string(),
2117 ..CliConfig::default()
2118 };
2119
2120 let keypair = Keypair::new();
2121 let pubkey = keypair.pubkey().to_string();
2122 config.signers = vec![&keypair];
2123 config.command = CliCommand::Address;
2124 assert_eq!(process_command(&config).unwrap(), pubkey);
2125
2126 config.command = CliCommand::Balance {
2127 pubkey: None,
2128 use_lamports_unit: true,
2129 };
2130 assert_eq!(process_command(&config).unwrap(), "50 lamports");
2131
2132 config.command = CliCommand::Balance {
2133 pubkey: None,
2134 use_lamports_unit: false,
2135 };
2136 assert_eq!(process_command(&config).unwrap(), "0.00000005 SOL");
2137
2138 let good_signature = bs58::decode(SIGNATURE)
2139 .into_vec()
2140 .map(Signature::try_from)
2141 .unwrap()
2142 .unwrap();
2143 config.command = CliCommand::Confirm(good_signature);
2144 assert_eq!(
2145 process_command(&config).unwrap(),
2146 format!("{:?}", TransactionConfirmationStatus::Finalized)
2147 );
2148
2149 let bob_keypair = Keypair::new();
2150 let bob_pubkey = bob_keypair.pubkey();
2151 let identity_keypair = Keypair::new();
2152 let vote_account_info_response = json!(Response {
2153 context: RpcResponseContext {
2154 slot: 1,
2155 api_version: None
2156 },
2157 value: json!({
2158 "data": ["", "base64"],
2159 "lamports": 50,
2160 "owner": "11111111111111111111111111111111",
2161 "executable": false,
2162 "rentEpoch": 1,
2163 }),
2164 });
2165 let mut mocks = HashMap::new();
2166 mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
2167 let rpc_client = Some(Arc::new(RpcClient::new_mock_with_mocks(
2168 "".to_string(),
2169 mocks,
2170 )));
2171 config.rpc_client = rpc_client;
2172 config.command = CliCommand::CreateVoteAccount {
2173 vote_account: 1,
2174 seed: None,
2175 identity_account: 2,
2176 authorized_voter: Some(bob_pubkey),
2177 authorized_withdrawer: bob_pubkey,
2178 commission: 0,
2179 sign_only: false,
2180 dump_transaction_message: false,
2181 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2182 nonce_account: None,
2183 nonce_authority: 0,
2184 memo: None,
2185 fee_payer: 0,
2186 compute_unit_price: None,
2187 };
2188 config.signers = vec![&keypair, &bob_keypair, &identity_keypair];
2189 let result = process_command(&config);
2190 assert!(result.is_ok());
2191
2192 let vote_account_info_response = json!(Response {
2193 context: RpcResponseContext {
2194 slot: 1,
2195 api_version: None
2196 },
2197 value: json!({
2198 "data": ["KLUv/QBYNQIAtAIBAAAAbnoc3Smwt4/ROvTFWY/v9O8qlxZuPKby5Pv8zYBQW/EFAAEAAB8ACQD6gx92zAiAAecDP4B2XeEBSIx7MQeung==", "base64+zstd"],
2199 "lamports": 42,
2200 "owner": "Vote111111111111111111111111111111111111111",
2201 "executable": false,
2202 "rentEpoch": 1,
2203 }),
2204 });
2205 let mut mocks = HashMap::new();
2206 mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
2207 let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
2208 let mut vote_config = CliConfig {
2209 rpc_client: Some(Arc::new(rpc_client)),
2210 json_rpc_url: "http://127.0.0.1:8899".to_string(),
2211 ..CliConfig::default()
2212 };
2213 let current_authority = keypair_from_seed(&[5; 32]).unwrap();
2214 let new_authorized_pubkey = solana_pubkey::new_rand();
2215 vote_config.signers = vec![¤t_authority];
2216 vote_config.command = CliCommand::VoteAuthorize {
2217 vote_account_pubkey: bob_pubkey,
2218 new_authorized_pubkey,
2219 vote_authorize: VoteAuthorize::Withdrawer,
2220 sign_only: false,
2221 dump_transaction_message: false,
2222 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2223 nonce_account: None,
2224 nonce_authority: 0,
2225 memo: None,
2226 fee_payer: 0,
2227 authorized: 0,
2228 new_authorized: None,
2229 compute_unit_price: None,
2230 };
2231 let result = process_command(&vote_config);
2232 assert!(result.is_ok());
2233
2234 let new_identity_keypair = Keypair::new();
2235 config.signers = vec![&keypair, &bob_keypair, &new_identity_keypair];
2236 config.command = CliCommand::VoteUpdateValidator {
2237 vote_account_pubkey: bob_pubkey,
2238 new_identity_account: 2,
2239 withdraw_authority: 1,
2240 sign_only: false,
2241 dump_transaction_message: false,
2242 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2243 nonce_account: None,
2244 nonce_authority: 0,
2245 memo: None,
2246 fee_payer: 0,
2247 compute_unit_price: None,
2248 };
2249 let result = process_command(&config);
2250 assert!(result.is_ok());
2251
2252 let bob_keypair = Keypair::new();
2253 let bob_pubkey = bob_keypair.pubkey();
2254 let custodian = solana_pubkey::new_rand();
2255 let vote_account_info_response = json!(Response {
2256 context: RpcResponseContext {
2257 slot: 1,
2258 api_version: None
2259 },
2260 value: json!({
2261 "data": ["", "base64"],
2262 "lamports": 50,
2263 "owner": "11111111111111111111111111111111",
2264 "executable": false,
2265 "rentEpoch": 1,
2266 }),
2267 });
2268 let mut mocks = HashMap::new();
2269 mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
2270 let rpc_client = Some(Arc::new(RpcClient::new_mock_with_mocks(
2271 "".to_string(),
2272 mocks,
2273 )));
2274 config.rpc_client = rpc_client;
2275 config.command = CliCommand::CreateStakeAccount {
2276 stake_account: 1,
2277 seed: None,
2278 staker: None,
2279 withdrawer: None,
2280 withdrawer_signer: None,
2281 lockup: Lockup {
2282 epoch: 0,
2283 unix_timestamp: 0,
2284 custodian,
2285 },
2286 amount: SpendAmount::Some(30),
2287 sign_only: false,
2288 dump_transaction_message: false,
2289 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2290 nonce_account: None,
2291 nonce_authority: 0,
2292 memo: None,
2293 fee_payer: 0,
2294 from: 0,
2295 compute_unit_price: None,
2296 };
2297 config.signers = vec![&keypair, &bob_keypair];
2298 let result = process_command(&config);
2299 assert!(result.is_ok());
2300
2301 let stake_account_pubkey = solana_pubkey::new_rand();
2302 let to_pubkey = solana_pubkey::new_rand();
2303 config.command = CliCommand::WithdrawStake {
2304 stake_account_pubkey,
2305 destination_account_pubkey: to_pubkey,
2306 amount: SpendAmount::All,
2307 withdraw_authority: 0,
2308 custodian: None,
2309 sign_only: false,
2310 dump_transaction_message: false,
2311 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2312 nonce_account: None,
2313 nonce_authority: 0,
2314 memo: None,
2315 seed: None,
2316 fee_payer: 0,
2317 compute_unit_price: None,
2318 };
2319 config.signers = vec![&keypair];
2320 let result = process_command(&config);
2321 assert!(result.is_ok());
2322
2323 let stake_account_pubkey = solana_pubkey::new_rand();
2324 config.command = CliCommand::DeactivateStake {
2325 stake_account_pubkey,
2326 stake_authority: 0,
2327 sign_only: false,
2328 dump_transaction_message: false,
2329 deactivate_delinquent: false,
2330 blockhash_query: BlockhashQuery::default(),
2331 nonce_account: None,
2332 nonce_authority: 0,
2333 memo: None,
2334 seed: None,
2335 fee_payer: 0,
2336 compute_unit_price: None,
2337 };
2338 let result = process_command(&config);
2339 assert!(result.is_ok());
2340
2341 let stake_account_pubkey = solana_pubkey::new_rand();
2342 let split_stake_account = Keypair::new();
2343 config.command = CliCommand::SplitStake {
2344 stake_account_pubkey,
2345 stake_authority: 0,
2346 sign_only: false,
2347 dump_transaction_message: false,
2348 blockhash_query: BlockhashQuery::default(),
2349 nonce_account: None,
2350 nonce_authority: 0,
2351 memo: None,
2352 split_stake_account: 1,
2353 seed: None,
2354 lamports: 200_000_000,
2355 fee_payer: 0,
2356 compute_unit_price: None,
2357 rent_exempt_reserve: None,
2358 };
2359 config.signers = vec![&keypair, &split_stake_account];
2360 let result = process_command(&config);
2361 assert!(result.is_ok());
2362
2363 let stake_account_pubkey = solana_pubkey::new_rand();
2364 let source_stake_account_pubkey = solana_pubkey::new_rand();
2365 let merge_stake_account = Keypair::new();
2366 config.command = CliCommand::MergeStake {
2367 stake_account_pubkey,
2368 source_stake_account_pubkey,
2369 stake_authority: 1,
2370 sign_only: false,
2371 dump_transaction_message: false,
2372 blockhash_query: BlockhashQuery::default(),
2373 nonce_account: None,
2374 nonce_authority: 0,
2375 memo: None,
2376 fee_payer: 0,
2377 compute_unit_price: None,
2378 };
2379 config.signers = vec![&keypair, &merge_stake_account];
2380 let result = process_command(&config);
2381 assert!(result.is_ok());
2382
2383 config.command = CliCommand::GetSlot;
2384 assert_eq!(process_command(&config).unwrap(), "0");
2385
2386 config.command = CliCommand::GetTransactionCount;
2387 assert_eq!(process_command(&config).unwrap(), "1234");
2388
2389 let from_pubkey = solana_pubkey::new_rand();
2391 config.signers = vec![];
2392 config.command = CliCommand::CreateAddressWithSeed {
2393 from_pubkey: Some(from_pubkey),
2394 seed: "seed".to_string(),
2395 program_id: stake::id(),
2396 };
2397 let address = process_command(&config);
2398 let expected_address =
2399 Pubkey::create_with_seed(&from_pubkey, "seed", &stake::id()).unwrap();
2400 assert_eq!(address.unwrap(), expected_address.to_string());
2401
2402 let to = solana_pubkey::new_rand();
2404 config.signers = vec![&keypair];
2405 config.command = CliCommand::Airdrop {
2406 pubkey: Some(to),
2407 lamports: 50,
2408 };
2409 assert!(process_command(&config).is_ok());
2410
2411 config.rpc_client = Some(Arc::new(RpcClient::new_mock("sig_not_found".to_string())));
2413 let missing_signature = bs58::decode("5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW")
2414 .into_vec()
2415 .map(Signature::try_from)
2416 .unwrap()
2417 .unwrap();
2418 config.command = CliCommand::Confirm(missing_signature);
2419 assert_eq!(process_command(&config).unwrap(), "Not found");
2420
2421 config.rpc_client = Some(Arc::new(RpcClient::new_mock("account_in_use".to_string())));
2423 let any_signature = bs58::decode(SIGNATURE)
2424 .into_vec()
2425 .map(Signature::try_from)
2426 .unwrap()
2427 .unwrap();
2428 config.command = CliCommand::Confirm(any_signature);
2429 assert_eq!(
2430 process_command(&config).unwrap(),
2431 format!("Transaction failed: {}", TransactionError::AccountInUse)
2432 );
2433
2434 config.rpc_client = Some(Arc::new(RpcClient::new_mock("fails".to_string())));
2436
2437 config.command = CliCommand::Airdrop {
2438 pubkey: None,
2439 lamports: 50,
2440 };
2441 assert!(process_command(&config).is_err());
2442
2443 config.command = CliCommand::Balance {
2444 pubkey: None,
2445 use_lamports_unit: false,
2446 };
2447 assert!(process_command(&config).is_err());
2448
2449 let bob_keypair = Keypair::new();
2450 let identity_keypair = Keypair::new();
2451 config.command = CliCommand::CreateVoteAccount {
2452 vote_account: 1,
2453 seed: None,
2454 identity_account: 2,
2455 authorized_voter: Some(bob_pubkey),
2456 authorized_withdrawer: bob_pubkey,
2457 commission: 0,
2458 sign_only: false,
2459 dump_transaction_message: false,
2460 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2461 nonce_account: None,
2462 nonce_authority: 0,
2463 memo: None,
2464 fee_payer: 0,
2465 compute_unit_price: None,
2466 };
2467 config.signers = vec![&keypair, &bob_keypair, &identity_keypair];
2468 assert!(process_command(&config).is_err());
2469
2470 config.command = CliCommand::VoteAuthorize {
2471 vote_account_pubkey: bob_pubkey,
2472 new_authorized_pubkey: bob_pubkey,
2473 vote_authorize: VoteAuthorize::Voter,
2474 sign_only: false,
2475 dump_transaction_message: false,
2476 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2477 nonce_account: None,
2478 nonce_authority: 0,
2479 memo: None,
2480 fee_payer: 0,
2481 authorized: 0,
2482 new_authorized: None,
2483 compute_unit_price: None,
2484 };
2485 assert!(process_command(&config).is_err());
2486
2487 config.command = CliCommand::VoteUpdateValidator {
2488 vote_account_pubkey: bob_pubkey,
2489 new_identity_account: 1,
2490 withdraw_authority: 1,
2491 sign_only: false,
2492 dump_transaction_message: false,
2493 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2494 nonce_account: None,
2495 nonce_authority: 0,
2496 memo: None,
2497 fee_payer: 0,
2498 compute_unit_price: None,
2499 };
2500 assert!(process_command(&config).is_err());
2501
2502 config.command = CliCommand::GetSlot;
2503 assert!(process_command(&config).is_err());
2504
2505 config.command = CliCommand::GetTransactionCount;
2506 assert!(process_command(&config).is_err());
2507
2508 let message = OffchainMessage::new(0, b"Test Message").unwrap();
2509 config.command = CliCommand::SignOffchainMessage {
2510 message: message.clone(),
2511 };
2512 config.signers = vec![&keypair];
2513 let result = process_command(&config);
2514 assert!(result.is_ok());
2515
2516 config.command = CliCommand::VerifyOffchainSignature {
2517 signer_pubkey: None,
2518 signature: result.unwrap().parse().unwrap(),
2519 message,
2520 };
2521 config.signers = vec![&keypair];
2522 let result = process_command(&config);
2523 assert!(result.is_ok());
2524 }
2525
2526 #[test]
2527 fn test_parse_transfer_subcommand() {
2528 let test_commands = get_clap_app("test", "desc", "version");
2529
2530 let default_keypair = Keypair::new();
2531 let default_keypair_file = make_tmp_path("keypair_file");
2532 write_keypair_file(&default_keypair, &default_keypair_file).unwrap();
2533 let default_signer = DefaultSigner::new("", &default_keypair_file);
2534
2535 let from_keypair = keypair_from_seed(&[0u8; 32]).unwrap();
2537 let from_pubkey = from_keypair.pubkey();
2538 let from_string = from_pubkey.to_string();
2539 let to_keypair = keypair_from_seed(&[1u8; 32]).unwrap();
2540 let to_pubkey = to_keypair.pubkey();
2541 let to_string = to_pubkey.to_string();
2542 let test_transfer = test_commands
2543 .clone()
2544 .get_matches_from(vec!["test", "transfer", &to_string, "42"]);
2545 assert_eq!(
2546 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2547 CliCommandInfo {
2548 command: CliCommand::Transfer {
2549 amount: SpendAmount::Some(42_000_000_000),
2550 to: to_pubkey,
2551 from: 0,
2552 sign_only: false,
2553 dump_transaction_message: false,
2554 allow_unfunded_recipient: false,
2555 no_wait: false,
2556 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2557 nonce_account: None,
2558 nonce_authority: 0,
2559 memo: None,
2560 fee_payer: 0,
2561 derived_address_seed: None,
2562 derived_address_program_id: None,
2563 compute_unit_price: None,
2564 },
2565 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2566 }
2567 );
2568
2569 let test_transfer = test_commands
2571 .clone()
2572 .get_matches_from(vec!["test", "transfer", &to_string, "ALL"]);
2573 assert_eq!(
2574 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2575 CliCommandInfo {
2576 command: CliCommand::Transfer {
2577 amount: SpendAmount::All,
2578 to: to_pubkey,
2579 from: 0,
2580 sign_only: false,
2581 dump_transaction_message: false,
2582 allow_unfunded_recipient: false,
2583 no_wait: false,
2584 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2585 nonce_account: None,
2586 nonce_authority: 0,
2587 memo: None,
2588 fee_payer: 0,
2589 derived_address_seed: None,
2590 derived_address_program_id: None,
2591 compute_unit_price: None,
2592 },
2593 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2594 }
2595 );
2596
2597 let test_transfer = test_commands.clone().get_matches_from(vec![
2599 "test",
2600 "transfer",
2601 "--no-wait",
2602 "--allow-unfunded-recipient",
2603 &to_string,
2604 "42",
2605 ]);
2606 assert_eq!(
2607 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2608 CliCommandInfo {
2609 command: CliCommand::Transfer {
2610 amount: SpendAmount::Some(42_000_000_000),
2611 to: to_pubkey,
2612 from: 0,
2613 sign_only: false,
2614 dump_transaction_message: false,
2615 allow_unfunded_recipient: true,
2616 no_wait: true,
2617 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2618 nonce_account: None,
2619 nonce_authority: 0,
2620 memo: None,
2621 fee_payer: 0,
2622 derived_address_seed: None,
2623 derived_address_program_id: None,
2624 compute_unit_price: None,
2625 },
2626 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2627 }
2628 );
2629
2630 let blockhash = Hash::new_from_array([1u8; 32]);
2632 let blockhash_string = blockhash.to_string();
2633 let test_transfer = test_commands.clone().get_matches_from(vec![
2634 "test",
2635 "transfer",
2636 &to_string,
2637 "42",
2638 "--blockhash",
2639 &blockhash_string,
2640 "--sign-only",
2641 ]);
2642 assert_eq!(
2643 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2644 CliCommandInfo {
2645 command: CliCommand::Transfer {
2646 amount: SpendAmount::Some(42_000_000_000),
2647 to: to_pubkey,
2648 from: 0,
2649 sign_only: true,
2650 dump_transaction_message: false,
2651 allow_unfunded_recipient: false,
2652 no_wait: false,
2653 blockhash_query: BlockhashQuery::None(blockhash),
2654 nonce_account: None,
2655 nonce_authority: 0,
2656 memo: None,
2657 fee_payer: 0,
2658 derived_address_seed: None,
2659 derived_address_program_id: None,
2660 compute_unit_price: None,
2661 },
2662 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2663 }
2664 );
2665
2666 let from_sig = from_keypair.sign_message(&[0u8]);
2668 let from_signer = format!("{from_pubkey}={from_sig}");
2669 let test_transfer = test_commands.clone().get_matches_from(vec![
2670 "test",
2671 "transfer",
2672 &to_string,
2673 "42",
2674 "--from",
2675 &from_string,
2676 "--fee-payer",
2677 &from_string,
2678 "--signer",
2679 &from_signer,
2680 "--blockhash",
2681 &blockhash_string,
2682 ]);
2683 assert_eq!(
2684 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2685 CliCommandInfo {
2686 command: CliCommand::Transfer {
2687 amount: SpendAmount::Some(42_000_000_000),
2688 to: to_pubkey,
2689 from: 0,
2690 sign_only: false,
2691 dump_transaction_message: false,
2692 allow_unfunded_recipient: false,
2693 no_wait: false,
2694 blockhash_query: BlockhashQuery::FeeCalculator(
2695 blockhash_query::Source::Cluster,
2696 blockhash
2697 ),
2698 nonce_account: None,
2699 nonce_authority: 0,
2700 memo: None,
2701 fee_payer: 0,
2702 derived_address_seed: None,
2703 derived_address_program_id: None,
2704 compute_unit_price: None,
2705 },
2706 signers: vec![Box::new(Presigner::new(&from_pubkey, &from_sig))],
2707 }
2708 );
2709
2710 let nonce_address = Pubkey::from([1u8; 32]);
2712 let nonce_address_string = nonce_address.to_string();
2713 let nonce_authority = keypair_from_seed(&[2u8; 32]).unwrap();
2714 let nonce_authority_file = make_tmp_path("nonce_authority_file");
2715 write_keypair_file(&nonce_authority, &nonce_authority_file).unwrap();
2716 let test_transfer = test_commands.clone().get_matches_from(vec![
2717 "test",
2718 "transfer",
2719 &to_string,
2720 "42",
2721 "--blockhash",
2722 &blockhash_string,
2723 "--nonce",
2724 &nonce_address_string,
2725 "--nonce-authority",
2726 &nonce_authority_file,
2727 ]);
2728 assert_eq!(
2729 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2730 CliCommandInfo {
2731 command: CliCommand::Transfer {
2732 amount: SpendAmount::Some(42_000_000_000),
2733 to: to_pubkey,
2734 from: 0,
2735 sign_only: false,
2736 dump_transaction_message: false,
2737 allow_unfunded_recipient: false,
2738 no_wait: false,
2739 blockhash_query: BlockhashQuery::FeeCalculator(
2740 blockhash_query::Source::NonceAccount(nonce_address),
2741 blockhash
2742 ),
2743 nonce_account: Some(nonce_address),
2744 nonce_authority: 1,
2745 memo: None,
2746 fee_payer: 0,
2747 derived_address_seed: None,
2748 derived_address_program_id: None,
2749 compute_unit_price: None,
2750 },
2751 signers: vec![
2752 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2753 Box::new(read_keypair_file(&nonce_authority_file).unwrap())
2754 ],
2755 }
2756 );
2757
2758 let derived_address_seed = "seed".to_string();
2760 let derived_address_program_id = "STAKE";
2761 let test_transfer = test_commands.clone().get_matches_from(vec![
2762 "test",
2763 "transfer",
2764 &to_string,
2765 "42",
2766 "--derived-address-seed",
2767 &derived_address_seed,
2768 "--derived-address-program-id",
2769 derived_address_program_id,
2770 ]);
2771 assert_eq!(
2772 parse_command(&test_transfer, &default_signer, &mut None).unwrap(),
2773 CliCommandInfo {
2774 command: CliCommand::Transfer {
2775 amount: SpendAmount::Some(42_000_000_000),
2776 to: to_pubkey,
2777 from: 0,
2778 sign_only: false,
2779 dump_transaction_message: false,
2780 allow_unfunded_recipient: false,
2781 no_wait: false,
2782 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2783 nonce_account: None,
2784 nonce_authority: 0,
2785 memo: None,
2786 fee_payer: 0,
2787 derived_address_seed: Some(derived_address_seed),
2788 derived_address_program_id: Some(stake::id()),
2789 compute_unit_price: None,
2790 },
2791 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
2792 }
2793 );
2794 }
2795
2796 #[test]
2797 fn test_cli_completions() {
2798 let mut clap_app = get_clap_app("test", "desc", "version");
2799
2800 let shells = [
2801 Shell::Bash,
2802 Shell::Fish,
2803 Shell::Zsh,
2804 Shell::PowerShell,
2805 Shell::Elvish,
2806 ];
2807
2808 for shell in shells {
2809 let mut buf: Vec<u8> = vec![];
2810
2811 clap_app.gen_completions_to("solana", shell, &mut buf);
2812
2813 assert!(!buf.is_empty());
2814 }
2815 }
2816}