Skip to main content

solana_cli/
stake.rs

1use {
2    crate::{
3        checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4        cli::{
5            CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6            log_instruction_custom_error,
7        },
8        compute_budget::{
9            ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
10        },
11        feature::get_feature_activation_epoch,
12        memo::WithMemo,
13        nonce::check_nonce_account,
14        spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balances},
15    },
16    clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand, value_t},
17    solana_account::{Account, from_account, state_traits::StateMut},
18    solana_clap_utils::{
19        ArgConstant,
20        compute_budget::{COMPUTE_UNIT_PRICE_ARG, ComputeUnitLimit, compute_unit_price_arg},
21        fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
22        hidden_unless_forced,
23        input_parsers::*,
24        input_validators::*,
25        keypair::{DefaultSigner, SignerIndex},
26        memo::{MEMO_ARG, memo_arg},
27        nonce::*,
28        offline::*,
29    },
30    solana_cli_output::{
31        self, CliBalance, CliEpochReward, CliStakeHistory, CliStakeHistoryEntry, CliStakeState,
32        CliStakeType, OutputFormat, ReturnSignersConfig, display::BuildBalanceMessageConfig,
33        return_signers_with_config,
34    },
35    solana_clock::{Clock, Epoch, SECONDS_PER_DAY, UnixTimestamp},
36    solana_commitment_config::CommitmentConfig,
37    solana_epoch_schedule::EpochSchedule,
38    solana_message::Message,
39    solana_native_token::Sol,
40    solana_pubkey::Pubkey,
41    solana_remote_wallet::remote_wallet::RemoteWalletManager,
42    solana_rpc_client::nonblocking::rpc_client::RpcClient,
43    solana_rpc_client_api::{
44        config::RpcGetVoteAccountsConfig,
45        request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
46        response::{RpcInflationReward, RpcVoteAccountStatus},
47    },
48    solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
49    solana_sdk_ids::{
50        system_program,
51        sysvar::{clock, stake_history},
52    },
53    solana_stake_interface::{
54        self as stake,
55        error::StakeError,
56        instruction::{self as stake_instruction, LockupArgs},
57        stake_history::StakeHistory,
58        state::{Authorized, Lockup, Meta, StakeActivationStatus, StakeAuthorize, StakeStateV2},
59        tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
60    },
61    solana_system_interface::{error::SystemError, instruction as system_instruction},
62    solana_transaction::Transaction,
63    std::{ops::Deref, rc::Rc},
64};
65
66pub const STAKE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
67    name: "stake_authority",
68    long: "stake-authority",
69    help: "Authorized staker [default: cli config keypair]",
70};
71
72pub const WITHDRAW_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant {
73    name: "withdraw_authority",
74    long: "withdraw-authority",
75    help: "Authorized withdrawer [default: cli config keypair]",
76};
77
78pub const CUSTODIAN_ARG: ArgConstant<'static> = ArgConstant {
79    name: "custodian",
80    long: "custodian",
81    help: "Authority to override account lockup",
82};
83
84fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
85    Arg::with_name(STAKE_AUTHORITY_ARG.name)
86        .long(STAKE_AUTHORITY_ARG.long)
87        .takes_value(true)
88        .value_name("KEYPAIR")
89        .validator(is_valid_signer)
90        .help(STAKE_AUTHORITY_ARG.help)
91}
92
93fn withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> {
94    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
95        .long(WITHDRAW_AUTHORITY_ARG.long)
96        .takes_value(true)
97        .value_name("KEYPAIR")
98        .validator(is_valid_signer)
99        .help(WITHDRAW_AUTHORITY_ARG.help)
100}
101
102fn custodian_arg<'a, 'b>() -> Arg<'a, 'b> {
103    Arg::with_name(CUSTODIAN_ARG.name)
104        .long(CUSTODIAN_ARG.long)
105        .takes_value(true)
106        .value_name("KEYPAIR")
107        .validator(is_valid_signer)
108        .help(CUSTODIAN_ARG.help)
109}
110
111pub(crate) struct StakeAuthorization {
112    authorization_type: StakeAuthorize,
113    new_authority_pubkey: Pubkey,
114    authority_pubkey: Option<Pubkey>,
115}
116
117#[derive(Debug, PartialEq, Eq)]
118pub struct StakeAuthorizationIndexed {
119    pub authorization_type: StakeAuthorize,
120    pub new_authority_pubkey: Pubkey,
121    pub authority: SignerIndex,
122    pub new_authority_signer: Option<SignerIndex>,
123}
124
125struct SignOnlySplitNeedsRent {}
126impl ArgsConfig for SignOnlySplitNeedsRent {
127    fn sign_only_arg<'a, 'b>(&self, arg: Arg<'a, 'b>) -> Arg<'a, 'b> {
128        arg.requires("rent_exempt_reserve_sol")
129    }
130}
131
132pub trait StakeSubCommands {
133    fn stake_subcommands(self) -> Self;
134}
135
136impl StakeSubCommands for App<'_, '_> {
137    fn stake_subcommands(self) -> Self {
138        self.subcommand(
139            SubCommand::with_name("create-stake-account")
140                .about("Create a stake account")
141                .arg(
142                    Arg::with_name("stake_account")
143                        .index(1)
144                        .value_name("STAKE_ACCOUNT_KEYPAIR")
145                        .takes_value(true)
146                        .required(true)
147                        .validator(is_valid_signer)
148                        .help(
149                            "Stake account to create (or base of derived address if --seed is \
150                             used)",
151                        ),
152                )
153                .arg(
154                    Arg::with_name("amount")
155                        .index(2)
156                        .value_name("AMOUNT")
157                        .takes_value(true)
158                        .validator(is_amount_or_all)
159                        .required(true)
160                        .help(
161                            "The amount to send to the stake account, in SOL; accepts keyword ALL",
162                        ),
163                )
164                .arg(pubkey!(
165                    Arg::with_name("custodian")
166                        .long("custodian")
167                        .value_name("PUBKEY"),
168                    "Authority to modify lockups."
169                ))
170                .arg(
171                    Arg::with_name("seed")
172                        .long("seed")
173                        .value_name("STRING")
174                        .takes_value(true)
175                        .help(
176                            "Seed for address generation; if specified, the resulting account \
177                             will be at a derived address of the STAKE_ACCOUNT_KEYPAIR pubkey",
178                        ),
179                )
180                .arg(
181                    Arg::with_name("lockup_epoch")
182                        .long("lockup-epoch")
183                        .value_name("NUMBER")
184                        .takes_value(true)
185                        .help(
186                            "The epoch height at which this account will be available for \
187                             withdrawal",
188                        ),
189                )
190                .arg(
191                    Arg::with_name("lockup_date")
192                        .long("lockup-date")
193                        .value_name("RFC3339 DATETIME")
194                        .validator(is_rfc3339_datetime)
195                        .takes_value(true)
196                        .help(
197                            "The date and time at which this account will be available for \
198                             withdrawal",
199                        ),
200                )
201                .arg(
202                    Arg::with_name(STAKE_AUTHORITY_ARG.name)
203                        .long(STAKE_AUTHORITY_ARG.long)
204                        .value_name("PUBKEY")
205                        .takes_value(true)
206                        .validator(is_valid_pubkey)
207                        .help(STAKE_AUTHORITY_ARG.help),
208                )
209                .arg(
210                    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
211                        .long(WITHDRAW_AUTHORITY_ARG.long)
212                        .value_name("PUBKEY")
213                        .takes_value(true)
214                        .validator(is_valid_pubkey)
215                        .help(WITHDRAW_AUTHORITY_ARG.help),
216                )
217                .arg(
218                    Arg::with_name("from")
219                        .long("from")
220                        .takes_value(true)
221                        .value_name("KEYPAIR")
222                        .validator(is_valid_signer)
223                        .help("Source account of funds [default: cli config keypair]"),
224                )
225                .offline_args()
226                .nonce_args(false)
227                .arg(fee_payer_arg())
228                .arg(memo_arg())
229                .arg(compute_unit_price_arg()),
230        )
231        .subcommand(
232            SubCommand::with_name("create-stake-account-checked")
233                .about("Create a stake account, checking the withdraw authority as a signer")
234                .arg(
235                    Arg::with_name("stake_account")
236                        .index(1)
237                        .value_name("STAKE_ACCOUNT_KEYPAIR")
238                        .takes_value(true)
239                        .required(true)
240                        .validator(is_valid_signer)
241                        .help(
242                            "Stake account to create (or base of derived address if --seed is \
243                             used)",
244                        ),
245                )
246                .arg(
247                    Arg::with_name("amount")
248                        .index(2)
249                        .value_name("AMOUNT")
250                        .takes_value(true)
251                        .validator(is_amount_or_all)
252                        .required(true)
253                        .help(
254                            "The amount to send to the stake account, in SOL; accepts keyword ALL",
255                        ),
256                )
257                .arg(
258                    Arg::with_name("seed")
259                        .long("seed")
260                        .value_name("STRING")
261                        .takes_value(true)
262                        .help(
263                            "Seed for address generation; if specified, the resulting account \
264                             will be at a derived address of the STAKE_ACCOUNT_KEYPAIR pubkey",
265                        ),
266                )
267                .arg(
268                    Arg::with_name(STAKE_AUTHORITY_ARG.name)
269                        .long(STAKE_AUTHORITY_ARG.long)
270                        .value_name("PUBKEY")
271                        .takes_value(true)
272                        .validator(is_valid_pubkey)
273                        .help(STAKE_AUTHORITY_ARG.help),
274                )
275                .arg(
276                    Arg::with_name(WITHDRAW_AUTHORITY_ARG.name)
277                        .long(WITHDRAW_AUTHORITY_ARG.long)
278                        .value_name("KEYPAIR")
279                        .takes_value(true)
280                        .validator(is_valid_signer)
281                        .help(WITHDRAW_AUTHORITY_ARG.help),
282                )
283                .arg(
284                    Arg::with_name("from")
285                        .long("from")
286                        .takes_value(true)
287                        .value_name("KEYPAIR")
288                        .validator(is_valid_signer)
289                        .help("Source account of funds [default: cli config keypair]"),
290                )
291                .offline_args()
292                .nonce_args(false)
293                .arg(fee_payer_arg())
294                .arg(memo_arg())
295                .arg(compute_unit_price_arg()),
296        )
297        .subcommand(
298            SubCommand::with_name("delegate-stake")
299                .about("Delegate stake to a vote account")
300                .arg(
301                    Arg::with_name("force")
302                        .long("force")
303                        .takes_value(false)
304                        .hidden(hidden_unless_forced()) // Don't document this argument to discourage its use
305                        .help("Override vote account sanity checks (use carefully!)"),
306                )
307                .arg(pubkey!(
308                    Arg::with_name("stake_account_pubkey")
309                        .index(1)
310                        .value_name("STAKE_ACCOUNT_ADDRESS")
311                        .required(true),
312                    "Stake account to delegate."
313                ))
314                .arg(pubkey!(
315                    Arg::with_name("vote_account_pubkey")
316                        .index(2)
317                        .value_name("VOTE_ACCOUNT_ADDRESS")
318                        .required(true),
319                    "Vote account to which the stake will be delegated."
320                ))
321                .arg(stake_authority_arg())
322                .offline_args()
323                .nonce_args(false)
324                .arg(fee_payer_arg())
325                .arg(memo_arg())
326                .arg(compute_unit_price_arg()),
327        )
328        .subcommand(
329            SubCommand::with_name("redelegate-stake")
330                .setting(AppSettings::Hidden)
331                .arg(
332                    // Consume all positional arguments
333                    Arg::with_name("arg")
334                        .multiple(true)
335                        .hidden(hidden_unless_forced()),
336                ),
337        )
338        .subcommand(
339            SubCommand::with_name("stake-authorize")
340                .about("Authorize a new signing keypair for the given stake account")
341                .arg(pubkey!(
342                    Arg::with_name("stake_account_pubkey")
343                        .required(true)
344                        .index(1)
345                        .value_name("STAKE_ACCOUNT_ADDRESS"),
346                    "Stake account in which to set a new authority."
347                ))
348                .arg(pubkey!(
349                    Arg::with_name("new_stake_authority")
350                        .long("new-stake-authority")
351                        .required_unless("new_withdraw_authority")
352                        .value_name("PUBKEY"),
353                    "New authorized staker."
354                ))
355                .arg(pubkey!(
356                    Arg::with_name("new_withdraw_authority")
357                        .long("new-withdraw-authority")
358                        .required_unless("new_stake_authority")
359                        .value_name("PUBKEY"),
360                    "New authorized withdrawer."
361                ))
362                .arg(stake_authority_arg())
363                .arg(withdraw_authority_arg())
364                .offline_args()
365                .nonce_args(false)
366                .arg(fee_payer_arg())
367                .arg(custodian_arg())
368                .arg(
369                    Arg::with_name("no_wait")
370                        .long("no-wait")
371                        .takes_value(false)
372                        .help(
373                            "Return signature immediately after submitting the transaction, \
374                             instead of waiting for confirmations",
375                        ),
376                )
377                .arg(memo_arg())
378                .arg(compute_unit_price_arg()),
379        )
380        .subcommand(
381            SubCommand::with_name("stake-authorize-checked")
382                .about(
383                    "Authorize a new signing keypair for the given stake account, checking the \
384                     authority as a signer",
385                )
386                .arg(pubkey!(
387                    Arg::with_name("stake_account_pubkey")
388                        .required(true)
389                        .index(1)
390                        .value_name("STAKE_ACCOUNT_ADDRESS"),
391                    "Stake account in which to set a new authority."
392                ))
393                .arg(
394                    Arg::with_name("new_stake_authority")
395                        .long("new-stake-authority")
396                        .value_name("KEYPAIR")
397                        .takes_value(true)
398                        .validator(is_valid_signer)
399                        .required_unless("new_withdraw_authority")
400                        .help("New authorized staker"),
401                )
402                .arg(
403                    Arg::with_name("new_withdraw_authority")
404                        .long("new-withdraw-authority")
405                        .value_name("KEYPAIR")
406                        .takes_value(true)
407                        .validator(is_valid_signer)
408                        .required_unless("new_stake_authority")
409                        .help("New authorized withdrawer"),
410                )
411                .arg(stake_authority_arg())
412                .arg(withdraw_authority_arg())
413                .offline_args()
414                .nonce_args(false)
415                .arg(fee_payer_arg())
416                .arg(custodian_arg())
417                .arg(
418                    Arg::with_name("no_wait")
419                        .long("no-wait")
420                        .takes_value(false)
421                        .help(
422                            "Return signature immediately after submitting the transaction, \
423                             instead of waiting for confirmations",
424                        ),
425                )
426                .arg(memo_arg())
427                .arg(compute_unit_price_arg()),
428        )
429        .subcommand(
430            SubCommand::with_name("deactivate-stake")
431                .about("Deactivate the delegated stake from the stake account")
432                .arg(pubkey!(
433                    Arg::with_name("stake_account_pubkey")
434                        .index(1)
435                        .value_name("STAKE_ACCOUNT_ADDRESS")
436                        .required(true),
437                    "Stake account to be deactivated (or base of derived address if --seed is \
438                     used)."
439                ))
440                .arg(
441                    Arg::with_name("seed")
442                        .long("seed")
443                        .value_name("STRING")
444                        .takes_value(true)
445                        .help(
446                            "Seed for address generation; if specified, the resulting account \
447                             will be at a derived address of STAKE_ACCOUNT_ADDRESS",
448                        ),
449                )
450                .arg(
451                    Arg::with_name("delinquent")
452                        .long("delinquent")
453                        .takes_value(false)
454                        .conflicts_with(SIGN_ONLY_ARG.name)
455                        .help(
456                            "Deactivate abandoned stake that is currently delegated to a \
457                             delinquent vote account",
458                        ),
459                )
460                .arg(stake_authority_arg())
461                .offline_args()
462                .nonce_args(false)
463                .arg(fee_payer_arg())
464                .arg(memo_arg())
465                .arg(compute_unit_price_arg()),
466        )
467        .subcommand(
468            SubCommand::with_name("split-stake")
469                .about("Duplicate a stake account, splitting the tokens between the two")
470                .arg(pubkey!(
471                    Arg::with_name("stake_account_pubkey")
472                        .index(1)
473                        .value_name("STAKE_ACCOUNT_ADDRESS")
474                        .required(true),
475                    "Stake account to split (or base of derived address if --seed is used)."
476                ))
477                .arg(
478                    Arg::with_name("split_stake_account")
479                        .index(2)
480                        .value_name("SPLIT_STAKE_ACCOUNT")
481                        .takes_value(true)
482                        .required(true)
483                        .validator(is_valid_signer)
484                        .help("Keypair of the new stake account"),
485                )
486                .arg(
487                    Arg::with_name("amount")
488                        .index(3)
489                        .value_name("AMOUNT")
490                        .takes_value(true)
491                        .validator(is_amount)
492                        .required(true)
493                        .help("The amount to move into the new stake account, in SOL"),
494                )
495                .arg(
496                    Arg::with_name("seed")
497                        .long("seed")
498                        .value_name("STRING")
499                        .takes_value(true)
500                        .help(
501                            "Seed for address generation; if specified, the resulting account \
502                             will be at a derived address of SPLIT_STAKE_ACCOUNT",
503                        ),
504                )
505                .arg(stake_authority_arg())
506                .offline_args_config(&SignOnlySplitNeedsRent {})
507                .nonce_args(false)
508                .arg(fee_payer_arg())
509                .arg(memo_arg())
510                .arg(compute_unit_price_arg())
511                .arg(
512                    Arg::with_name("rent_exempt_reserve_sol")
513                        .long("rent-exempt-reserve-sol")
514                        .value_name("AMOUNT")
515                        .takes_value(true)
516                        .validator(is_amount)
517                        .help(
518                            "The rent-exempt amount to move into the new stake account, in SOL. \
519                             Required for offline signing.",
520                        ),
521                ),
522        )
523        .subcommand(
524            SubCommand::with_name("merge-stake")
525                .about("Merges one stake account into another")
526                .arg(pubkey!(
527                    Arg::with_name("stake_account_pubkey")
528                        .index(1)
529                        .value_name("STAKE_ACCOUNT_ADDRESS")
530                        .required(true),
531                    "Stake account to merge into."
532                ))
533                .arg(pubkey!(
534                    Arg::with_name("source_stake_account_pubkey")
535                        .index(2)
536                        .value_name("SOURCE_STAKE_ACCOUNT_ADDRESS")
537                        .required(true),
538                    "Source stake account for the merge. If successful, this stake account will \
539                     no longer exist after the merge."
540                ))
541                .arg(stake_authority_arg())
542                .offline_args()
543                .nonce_args(false)
544                .arg(fee_payer_arg())
545                .arg(memo_arg())
546                .arg(compute_unit_price_arg()),
547        )
548        .subcommand(
549            SubCommand::with_name("withdraw-stake")
550                .about("Withdraw the unstaked SOL from the stake account")
551                .arg(pubkey!(
552                    Arg::with_name("stake_account_pubkey")
553                        .index(1)
554                        .value_name("STAKE_ACCOUNT_ADDRESS")
555                        .required(true),
556                    "Stake account from which to withdraw (or base of derived address if --seed \
557                     is used)."
558                ))
559                .arg(pubkey!(
560                    Arg::with_name("destination_account_pubkey")
561                        .index(2)
562                        .value_name("RECIPIENT_ADDRESS")
563                        .required(true),
564                    "Recipient of withdrawn stake."
565                ))
566                .arg(
567                    Arg::with_name("amount")
568                        .index(3)
569                        .value_name("AMOUNT")
570                        .takes_value(true)
571                        .validator(is_amount_or_all_or_available)
572                        .required(true)
573                        .help(
574                            "The amount to withdraw from the stake account, in SOL; accepts \
575                             keywords ALL or AVAILABLE",
576                        ),
577                )
578                .arg(
579                    Arg::with_name("seed")
580                        .long("seed")
581                        .value_name("STRING")
582                        .takes_value(true)
583                        .help(
584                            "Seed for address generation; if specified, the resulting account \
585                             will be at a derived address of STAKE_ACCOUNT_ADDRESS",
586                        ),
587                )
588                .arg(withdraw_authority_arg())
589                .offline_args()
590                .nonce_args(false)
591                .arg(fee_payer_arg())
592                .arg(custodian_arg())
593                .arg(memo_arg())
594                .arg(compute_unit_price_arg()),
595        )
596        .subcommand(
597            SubCommand::with_name("stake-set-lockup")
598                .about("Set Lockup for the stake account")
599                .arg(pubkey!(
600                    Arg::with_name("stake_account_pubkey")
601                        .index(1)
602                        .value_name("STAKE_ACCOUNT_ADDRESS")
603                        .required(true),
604                    "Stake account for which to set lockup parameters."
605                ))
606                .arg(
607                    Arg::with_name("lockup_epoch")
608                        .long("lockup-epoch")
609                        .value_name("NUMBER")
610                        .takes_value(true)
611                        .help(
612                            "The epoch height at which this account will be available for \
613                             withdrawal",
614                        ),
615                )
616                .arg(
617                    Arg::with_name("lockup_date")
618                        .long("lockup-date")
619                        .value_name("RFC3339 DATETIME")
620                        .validator(is_rfc3339_datetime)
621                        .takes_value(true)
622                        .help(
623                            "The date and time at which this account will be available for \
624                             withdrawal",
625                        ),
626                )
627                .arg(pubkey!(
628                    Arg::with_name("new_custodian")
629                        .long("new-custodian")
630                        .value_name("PUBKEY"),
631                    "New lockup custodian."
632                ))
633                .group(
634                    ArgGroup::with_name("lockup_details")
635                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
636                        .multiple(true)
637                        .required(true),
638                )
639                .arg(
640                    Arg::with_name("custodian")
641                        .long("custodian")
642                        .takes_value(true)
643                        .value_name("KEYPAIR")
644                        .validator(is_valid_signer)
645                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
646                )
647                .offline_args()
648                .nonce_args(false)
649                .arg(fee_payer_arg())
650                .arg(memo_arg())
651                .arg(compute_unit_price_arg()),
652        )
653        .subcommand(
654            SubCommand::with_name("stake-set-lockup-checked")
655                .about("Set Lockup for the stake account, checking the new authority as a signer")
656                .arg(pubkey!(
657                    Arg::with_name("stake_account_pubkey")
658                        .index(1)
659                        .value_name("STAKE_ACCOUNT_ADDRESS")
660                        .required(true),
661                    "Stake account for which to set lockup parameters."
662                ))
663                .arg(
664                    Arg::with_name("lockup_epoch")
665                        .long("lockup-epoch")
666                        .value_name("NUMBER")
667                        .takes_value(true)
668                        .help(
669                            "The epoch height at which this account will be available for \
670                             withdrawal",
671                        ),
672                )
673                .arg(
674                    Arg::with_name("lockup_date")
675                        .long("lockup-date")
676                        .value_name("RFC3339 DATETIME")
677                        .validator(is_rfc3339_datetime)
678                        .takes_value(true)
679                        .help(
680                            "The date and time at which this account will be available for \
681                             withdrawal",
682                        ),
683                )
684                .arg(
685                    Arg::with_name("new_custodian")
686                        .long("new-custodian")
687                        .value_name("KEYPAIR")
688                        .takes_value(true)
689                        .validator(is_valid_signer)
690                        .help("Keypair of a new lockup custodian"),
691                )
692                .group(
693                    ArgGroup::with_name("lockup_details")
694                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
695                        .multiple(true)
696                        .required(true),
697                )
698                .arg(
699                    Arg::with_name("custodian")
700                        .long("custodian")
701                        .takes_value(true)
702                        .value_name("KEYPAIR")
703                        .validator(is_valid_signer)
704                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
705                )
706                .offline_args()
707                .nonce_args(false)
708                .arg(fee_payer_arg())
709                .arg(memo_arg())
710                .arg(compute_unit_price_arg()),
711        )
712        .subcommand(
713            SubCommand::with_name("stake-account")
714                .about("Show the contents of a stake account")
715                .alias("show-stake-account")
716                .arg(pubkey!(
717                    Arg::with_name("stake_account_pubkey")
718                        .index(1)
719                        .value_name("STAKE_ACCOUNT_ADDRESS")
720                        .required(true),
721                    "Stake account to display."
722                ))
723                .arg(
724                    Arg::with_name("lamports")
725                        .long("lamports")
726                        .takes_value(false)
727                        .help("Display balance in lamports instead of SOL"),
728                )
729                .arg(
730                    Arg::with_name("with_rewards")
731                        .long("with-rewards")
732                        .takes_value(false)
733                        .help("Display inflation rewards"),
734                )
735                .arg(
736                    Arg::with_name("csv")
737                        .long("csv")
738                        .takes_value(false)
739                        .help("Format stake rewards data in csv"),
740                )
741                .arg(
742                    Arg::with_name("starting_epoch")
743                        .long("starting-epoch")
744                        .takes_value(true)
745                        .value_name("NUM")
746                        .requires("with_rewards")
747                        .help("Start displaying from epoch NUM"),
748                )
749                .arg(
750                    Arg::with_name("num_rewards_epochs")
751                        .long("num-rewards-epochs")
752                        .takes_value(true)
753                        .value_name("NUM")
754                        .validator(|s| is_within_range(s, 1..=50))
755                        .default_value_if("with_rewards", None, "1")
756                        .requires("with_rewards")
757                        .help(
758                            "Display rewards for NUM recent epochs, max 10 [default: latest epoch \
759                             only]",
760                        ),
761                ),
762        )
763        .subcommand(
764            SubCommand::with_name("stake-history")
765                .about("Show the stake history")
766                .alias("show-stake-history")
767                .arg(
768                    Arg::with_name("lamports")
769                        .long("lamports")
770                        .takes_value(false)
771                        .help("Display balance in lamports instead of SOL"),
772                )
773                .arg(
774                    Arg::with_name("limit")
775                        .long("limit")
776                        .takes_value(true)
777                        .value_name("NUM")
778                        .default_value("10")
779                        .validator(|s| s.parse::<usize>().map(|_| ()).map_err(|e| e.to_string()))
780                        .help(
781                            "Display NUM recent epochs worth of stake history in text mode. 0 for \
782                             all",
783                        ),
784                ),
785        )
786        .subcommand(
787            SubCommand::with_name("stake-minimum-delegation")
788                .about("Get the stake minimum delegation amount")
789                .arg(
790                    Arg::with_name("lamports")
791                        .long("lamports")
792                        .takes_value(false)
793                        .help("Display minimum delegation in lamports instead of SOL"),
794                ),
795        )
796    }
797}
798
799pub fn parse_create_stake_account(
800    matches: &ArgMatches<'_>,
801    default_signer: &DefaultSigner,
802    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
803    checked: bool,
804) -> Result<CliCommandInfo, CliError> {
805    let seed = matches.value_of("seed").map(|s| s.to_string());
806    let epoch = value_of(matches, "lockup_epoch").unwrap_or(0);
807    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date").unwrap_or(0);
808    let custodian = pubkey_of_signer(matches, "custodian", wallet_manager)?.unwrap_or_default();
809    let staker = pubkey_of_signer(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
810
811    let (withdrawer_signer, withdrawer) = if checked {
812        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
813    } else {
814        (
815            None,
816            pubkey_of_signer(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?,
817        )
818    };
819
820    let amount = SpendAmount::new_from_matches(matches, "amount")?;
821    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
822    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
823    let blockhash_query = BlockhashQuery::new_from_matches(matches);
824    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
825    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
826    let (nonce_authority, nonce_authority_pubkey) =
827        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
828    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
829    let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
830    let (stake_account, stake_account_pubkey) =
831        signer_of(matches, "stake_account", wallet_manager)?;
832
833    let mut bulk_signers = vec![fee_payer, from, stake_account];
834    if nonce_account.is_some() {
835        bulk_signers.push(nonce_authority);
836    }
837    if withdrawer_signer.is_some() {
838        bulk_signers.push(withdrawer_signer);
839    }
840    let signer_info =
841        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
842    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
843
844    Ok(CliCommandInfo {
845        command: CliCommand::CreateStakeAccount {
846            stake_account: signer_info.index_of(stake_account_pubkey).unwrap(),
847            seed,
848            staker,
849            withdrawer,
850            withdrawer_signer: if checked {
851                signer_info.index_of(withdrawer)
852            } else {
853                None
854            },
855            lockup: Lockup {
856                unix_timestamp,
857                epoch,
858                custodian,
859            },
860            amount,
861            sign_only,
862            dump_transaction_message,
863            blockhash_query,
864            nonce_account,
865            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
866            memo,
867            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
868            from: signer_info.index_of(from_pubkey).unwrap(),
869            compute_unit_price,
870        },
871        signers: signer_info.signers,
872    })
873}
874
875pub fn parse_stake_delegate_stake(
876    matches: &ArgMatches<'_>,
877    default_signer: &DefaultSigner,
878    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
879) -> Result<CliCommandInfo, CliError> {
880    let stake_account_pubkey =
881        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
882    let vote_account_pubkey =
883        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
884    let force = matches.is_present("force");
885    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
886    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
887    let blockhash_query = BlockhashQuery::new_from_matches(matches);
888    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
889    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
890    let (stake_authority, stake_authority_pubkey) =
891        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
892    let (nonce_authority, nonce_authority_pubkey) =
893        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
894    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
895
896    let mut bulk_signers = vec![stake_authority, fee_payer];
897    if nonce_account.is_some() {
898        bulk_signers.push(nonce_authority);
899    }
900    let signer_info =
901        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
902    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
903
904    Ok(CliCommandInfo {
905        command: CliCommand::DelegateStake {
906            stake_account_pubkey,
907            vote_account_pubkey,
908            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
909            force,
910            sign_only,
911            dump_transaction_message,
912            blockhash_query,
913            nonce_account,
914            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
915            memo,
916            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
917            compute_unit_price,
918        },
919        signers: signer_info.signers,
920    })
921}
922
923pub fn parse_stake_authorize(
924    matches: &ArgMatches<'_>,
925    default_signer: &DefaultSigner,
926    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
927    checked: bool,
928) -> Result<CliCommandInfo, CliError> {
929    let stake_account_pubkey =
930        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
931
932    let mut new_authorizations = Vec::new();
933    let mut bulk_signers = Vec::new();
934
935    let (new_staker_signer, new_staker) = if checked {
936        signer_of(matches, "new_stake_authority", wallet_manager)?
937    } else {
938        (
939            None,
940            pubkey_of_signer(matches, "new_stake_authority", wallet_manager)?,
941        )
942    };
943
944    if let Some(new_authority_pubkey) = new_staker {
945        let (authority, authority_pubkey) = {
946            let (authority, authority_pubkey) =
947                signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
948            // Withdraw authority may also change the staker
949            if authority.is_none() {
950                signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
951            } else {
952                (authority, authority_pubkey)
953            }
954        };
955        new_authorizations.push(StakeAuthorization {
956            authorization_type: StakeAuthorize::Staker,
957            new_authority_pubkey,
958            authority_pubkey,
959        });
960        bulk_signers.push(authority);
961        if new_staker.is_some() {
962            bulk_signers.push(new_staker_signer);
963        }
964    };
965
966    let (new_withdrawer_signer, new_withdrawer) = if checked {
967        signer_of(matches, "new_withdraw_authority", wallet_manager)?
968    } else {
969        (
970            None,
971            pubkey_of_signer(matches, "new_withdraw_authority", wallet_manager)?,
972        )
973    };
974
975    if let Some(new_authority_pubkey) = new_withdrawer {
976        let (authority, authority_pubkey) =
977            signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
978        new_authorizations.push(StakeAuthorization {
979            authorization_type: StakeAuthorize::Withdrawer,
980            new_authority_pubkey,
981            authority_pubkey,
982        });
983        bulk_signers.push(authority);
984        if new_withdrawer_signer.is_some() {
985            bulk_signers.push(new_withdrawer_signer);
986        }
987    };
988    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
989    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
990    let blockhash_query = BlockhashQuery::new_from_matches(matches);
991    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
992    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
993    let (nonce_authority, nonce_authority_pubkey) =
994        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
995    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
996    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
997    let no_wait = matches.is_present("no_wait");
998
999    bulk_signers.push(fee_payer);
1000    if nonce_account.is_some() {
1001        bulk_signers.push(nonce_authority);
1002    }
1003    if custodian.is_some() {
1004        bulk_signers.push(custodian);
1005    }
1006    let signer_info =
1007        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1008    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1009
1010    if new_authorizations.is_empty() {
1011        return Err(CliError::BadParameter(
1012            "New authorization list must include at least one authority".to_string(),
1013        ));
1014    }
1015    let new_authorizations = new_authorizations
1016        .into_iter()
1017        .map(
1018            |StakeAuthorization {
1019                 authorization_type,
1020                 new_authority_pubkey,
1021                 authority_pubkey,
1022             }| {
1023                StakeAuthorizationIndexed {
1024                    authorization_type,
1025                    new_authority_pubkey,
1026                    authority: signer_info.index_of(authority_pubkey).unwrap(),
1027                    new_authority_signer: signer_info.index_of(Some(new_authority_pubkey)),
1028                }
1029            },
1030        )
1031        .collect();
1032
1033    Ok(CliCommandInfo {
1034        command: CliCommand::StakeAuthorize {
1035            stake_account_pubkey,
1036            new_authorizations,
1037            sign_only,
1038            dump_transaction_message,
1039            blockhash_query,
1040            nonce_account,
1041            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1042            memo,
1043            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1044            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
1045            no_wait,
1046            compute_unit_price,
1047        },
1048        signers: signer_info.signers,
1049    })
1050}
1051
1052pub fn parse_split_stake(
1053    matches: &ArgMatches<'_>,
1054    default_signer: &DefaultSigner,
1055    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1056) -> Result<CliCommandInfo, CliError> {
1057    let stake_account_pubkey =
1058        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1059    let (split_stake_account, split_stake_account_pubkey) =
1060        signer_of(matches, "split_stake_account", wallet_manager)?;
1061    let lamports = lamports_of_sol(matches, "amount").unwrap();
1062    let seed = matches.value_of("seed").map(|s| s.to_string());
1063
1064    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1065    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1066    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1067    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1068    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1069    let (stake_authority, stake_authority_pubkey) =
1070        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1071    let (nonce_authority, nonce_authority_pubkey) =
1072        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1073    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1074
1075    let mut bulk_signers = vec![stake_authority, fee_payer, split_stake_account];
1076    if nonce_account.is_some() {
1077        bulk_signers.push(nonce_authority);
1078    }
1079    let signer_info =
1080        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1081    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1082    let rent_exempt_reserve = lamports_of_sol(matches, "rent_exempt_reserve_sol");
1083
1084    Ok(CliCommandInfo {
1085        command: CliCommand::SplitStake {
1086            stake_account_pubkey,
1087            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1088            sign_only,
1089            dump_transaction_message,
1090            blockhash_query,
1091            nonce_account,
1092            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1093            memo,
1094            split_stake_account: signer_info.index_of(split_stake_account_pubkey).unwrap(),
1095            seed,
1096            lamports,
1097            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1098            compute_unit_price,
1099            rent_exempt_reserve,
1100        },
1101        signers: signer_info.signers,
1102    })
1103}
1104
1105pub fn parse_merge_stake(
1106    matches: &ArgMatches<'_>,
1107    default_signer: &DefaultSigner,
1108    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1109) -> Result<CliCommandInfo, CliError> {
1110    let stake_account_pubkey =
1111        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1112
1113    let source_stake_account_pubkey = pubkey_of(matches, "source_stake_account_pubkey").unwrap();
1114
1115    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1116    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1117    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1118    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1119    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1120    let (stake_authority, stake_authority_pubkey) =
1121        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1122    let (nonce_authority, nonce_authority_pubkey) =
1123        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1124    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1125
1126    let mut bulk_signers = vec![stake_authority, fee_payer];
1127    if nonce_account.is_some() {
1128        bulk_signers.push(nonce_authority);
1129    }
1130    let signer_info =
1131        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1132    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1133
1134    Ok(CliCommandInfo {
1135        command: CliCommand::MergeStake {
1136            stake_account_pubkey,
1137            source_stake_account_pubkey,
1138            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1139            sign_only,
1140            dump_transaction_message,
1141            blockhash_query,
1142            nonce_account,
1143            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1144            memo,
1145            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1146            compute_unit_price,
1147        },
1148        signers: signer_info.signers,
1149    })
1150}
1151
1152pub fn parse_stake_deactivate_stake(
1153    matches: &ArgMatches<'_>,
1154    default_signer: &DefaultSigner,
1155    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1156) -> Result<CliCommandInfo, CliError> {
1157    let stake_account_pubkey =
1158        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1159    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1160    let deactivate_delinquent = matches.is_present("delinquent");
1161    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1162    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1163    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1164    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1165    let seed = value_t!(matches, "seed", String).ok();
1166
1167    let (stake_authority, stake_authority_pubkey) =
1168        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1169    let (nonce_authority, nonce_authority_pubkey) =
1170        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1171    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1172
1173    let mut bulk_signers = vec![stake_authority, fee_payer];
1174    if nonce_account.is_some() {
1175        bulk_signers.push(nonce_authority);
1176    }
1177    let signer_info =
1178        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1179    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1180
1181    Ok(CliCommandInfo {
1182        command: CliCommand::DeactivateStake {
1183            stake_account_pubkey,
1184            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1185            sign_only,
1186            deactivate_delinquent,
1187            dump_transaction_message,
1188            blockhash_query,
1189            nonce_account,
1190            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1191            memo,
1192            seed,
1193            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1194            compute_unit_price,
1195        },
1196        signers: signer_info.signers,
1197    })
1198}
1199
1200pub fn parse_stake_withdraw_stake(
1201    matches: &ArgMatches<'_>,
1202    default_signer: &DefaultSigner,
1203    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1204) -> Result<CliCommandInfo, CliError> {
1205    let stake_account_pubkey =
1206        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1207    let destination_account_pubkey =
1208        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
1209    let amount = SpendAmount::new_from_matches(matches, "amount")?;
1210    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1211    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1212    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1213    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1214    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1215    let seed = value_t!(matches, "seed", String).ok();
1216    let (withdraw_authority, withdraw_authority_pubkey) =
1217        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
1218    let (nonce_authority, nonce_authority_pubkey) =
1219        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1220    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1221    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
1222
1223    let mut bulk_signers = vec![withdraw_authority, fee_payer];
1224    if nonce_account.is_some() {
1225        bulk_signers.push(nonce_authority);
1226    }
1227    if custodian.is_some() {
1228        bulk_signers.push(custodian);
1229    }
1230    let signer_info =
1231        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1232    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1233
1234    Ok(CliCommandInfo {
1235        command: CliCommand::WithdrawStake {
1236            stake_account_pubkey,
1237            destination_account_pubkey,
1238            amount,
1239            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
1240            sign_only,
1241            dump_transaction_message,
1242            blockhash_query,
1243            nonce_account,
1244            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1245            memo,
1246            seed,
1247            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1248            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
1249            compute_unit_price,
1250        },
1251        signers: signer_info.signers,
1252    })
1253}
1254
1255pub fn parse_stake_set_lockup(
1256    matches: &ArgMatches<'_>,
1257    default_signer: &DefaultSigner,
1258    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1259    checked: bool,
1260) -> Result<CliCommandInfo, CliError> {
1261    let stake_account_pubkey =
1262        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1263    let epoch = value_of(matches, "lockup_epoch");
1264    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date");
1265
1266    let (new_custodian_signer, new_custodian) = if checked {
1267        signer_of(matches, "new_custodian", wallet_manager)?
1268    } else {
1269        (
1270            None,
1271            pubkey_of_signer(matches, "new_custodian", wallet_manager)?,
1272        )
1273    };
1274
1275    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1276    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1277    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1278    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1279    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1280
1281    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
1282    let (nonce_authority, nonce_authority_pubkey) =
1283        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1284    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1285
1286    let mut bulk_signers = vec![custodian, fee_payer];
1287    if nonce_account.is_some() {
1288        bulk_signers.push(nonce_authority);
1289    }
1290    if new_custodian_signer.is_some() {
1291        bulk_signers.push(new_custodian_signer);
1292    }
1293    let signer_info =
1294        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1295    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1296
1297    Ok(CliCommandInfo {
1298        command: CliCommand::StakeSetLockup {
1299            stake_account_pubkey,
1300            lockup: LockupArgs {
1301                custodian: new_custodian,
1302                epoch,
1303                unix_timestamp,
1304            },
1305            new_custodian_signer: if checked {
1306                signer_info.index_of(new_custodian)
1307            } else {
1308                None
1309            },
1310            custodian: signer_info.index_of(custodian_pubkey).unwrap(),
1311            sign_only,
1312            dump_transaction_message,
1313            blockhash_query,
1314            nonce_account,
1315            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1316            memo,
1317            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1318            compute_unit_price,
1319        },
1320        signers: signer_info.signers,
1321    })
1322}
1323
1324pub fn parse_show_stake_account(
1325    matches: &ArgMatches<'_>,
1326    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1327) -> Result<CliCommandInfo, CliError> {
1328    let stake_account_pubkey =
1329        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1330    let use_lamports_unit = matches.is_present("lamports");
1331    let use_csv = matches.is_present("csv");
1332    let with_rewards = if matches.is_present("with_rewards") {
1333        Some(value_of(matches, "num_rewards_epochs").unwrap())
1334    } else {
1335        None
1336    };
1337    let starting_epoch = value_of(matches, "starting_epoch");
1338    Ok(CliCommandInfo::without_signers(
1339        CliCommand::ShowStakeAccount {
1340            pubkey: stake_account_pubkey,
1341            use_lamports_unit,
1342            with_rewards,
1343            use_csv,
1344            starting_epoch,
1345        },
1346    ))
1347}
1348
1349pub fn parse_show_stake_history(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
1350    let use_lamports_unit = matches.is_present("lamports");
1351    let limit_results = value_of(matches, "limit").unwrap();
1352    Ok(CliCommandInfo::without_signers(
1353        CliCommand::ShowStakeHistory {
1354            use_lamports_unit,
1355            limit_results,
1356        },
1357    ))
1358}
1359
1360pub fn parse_stake_minimum_delegation(
1361    matches: &ArgMatches<'_>,
1362) -> Result<CliCommandInfo, CliError> {
1363    let use_lamports_unit = matches.is_present("lamports");
1364    Ok(CliCommandInfo::without_signers(
1365        CliCommand::StakeMinimumDelegation { use_lamports_unit },
1366    ))
1367}
1368
1369#[allow(clippy::too_many_arguments)]
1370pub async fn process_create_stake_account(
1371    rpc_client: &RpcClient,
1372    config: &CliConfig<'_>,
1373    stake_account: SignerIndex,
1374    seed: &Option<String>,
1375    staker: &Option<Pubkey>,
1376    withdrawer: &Option<Pubkey>,
1377    withdrawer_signer: Option<SignerIndex>,
1378    lockup: &Lockup,
1379    mut amount: SpendAmount,
1380    sign_only: bool,
1381    dump_transaction_message: bool,
1382    blockhash_query: &BlockhashQuery,
1383    nonce_account: Option<&Pubkey>,
1384    nonce_authority: SignerIndex,
1385    memo: Option<&String>,
1386    fee_payer: SignerIndex,
1387    from: SignerIndex,
1388    compute_unit_price: Option<u64>,
1389) -> ProcessResult {
1390    let stake_account = config.signers[stake_account];
1391    let stake_account_address = if let Some(seed) = seed {
1392        Pubkey::create_with_seed(&stake_account.pubkey(), seed, &stake::program::id())?
1393    } else {
1394        stake_account.pubkey()
1395    };
1396    let from = config.signers[from];
1397    check_unique_pubkeys(
1398        (&from.pubkey(), "from keypair".to_string()),
1399        (&stake_account_address, "stake_account".to_string()),
1400    )?;
1401
1402    let fee_payer = config.signers[fee_payer];
1403    let nonce_authority = config.signers[nonce_authority];
1404
1405    let compute_unit_limit = match blockhash_query {
1406        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1407        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1408    };
1409    let build_message = |lamports| {
1410        let authorized = Authorized {
1411            staker: staker.unwrap_or(from.pubkey()),
1412            withdrawer: withdrawer.unwrap_or(from.pubkey()),
1413        };
1414
1415        let ixs = match (seed, withdrawer_signer) {
1416            (Some(seed), Some(_withdrawer_signer)) => {
1417                stake_instruction::create_account_with_seed_checked(
1418                    &from.pubkey(),          // from
1419                    &stake_account_address,  // to
1420                    &stake_account.pubkey(), // base
1421                    seed,                    // seed
1422                    &authorized,
1423                    lamports,
1424                )
1425            }
1426            (Some(seed), None) => stake_instruction::create_account_with_seed(
1427                &from.pubkey(),          // from
1428                &stake_account_address,  // to
1429                &stake_account.pubkey(), // base
1430                seed,                    // seed
1431                &authorized,
1432                lockup,
1433                lamports,
1434            ),
1435            (None, Some(_withdrawer_signer)) => stake_instruction::create_account_checked(
1436                &from.pubkey(),
1437                &stake_account.pubkey(),
1438                &authorized,
1439                lamports,
1440            ),
1441            (None, None) => stake_instruction::create_account(
1442                &from.pubkey(),
1443                &stake_account.pubkey(),
1444                &authorized,
1445                lockup,
1446                lamports,
1447            ),
1448        }
1449        .with_memo(memo)
1450        .with_compute_unit_config(&ComputeUnitConfig {
1451            compute_unit_price,
1452            compute_unit_limit,
1453        });
1454        if let Some(nonce_account) = &nonce_account {
1455            Message::new_with_nonce(
1456                ixs,
1457                Some(&fee_payer.pubkey()),
1458                nonce_account,
1459                &nonce_authority.pubkey(),
1460            )
1461        } else {
1462            Message::new(&ixs, Some(&fee_payer.pubkey()))
1463        }
1464    };
1465
1466    let recent_blockhash = blockhash_query
1467        .get_blockhash(rpc_client, config.commitment)
1468        .await?;
1469
1470    if !sign_only && amount == SpendAmount::All {
1471        let minimum_balance = rpc_client
1472            .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())
1473            .await?;
1474        amount = SpendAmount::AllForAccountCreation {
1475            create_account_min_balance: minimum_balance,
1476        };
1477    }
1478
1479    let (message, lamports) = resolve_spend_tx_and_check_account_balances(
1480        rpc_client,
1481        sign_only,
1482        amount,
1483        &recent_blockhash,
1484        &from.pubkey(),
1485        &fee_payer.pubkey(),
1486        compute_unit_limit,
1487        build_message,
1488        config.commitment,
1489    )
1490    .await?;
1491
1492    if !sign_only {
1493        if let Ok(stake_account) = rpc_client.get_account(&stake_account_address).await {
1494            let err_msg = if stake_account.owner == stake::program::id() {
1495                format!("Stake account {stake_account_address} already exists")
1496            } else {
1497                format!("Account {stake_account_address} already exists and is not a stake account")
1498            };
1499            return Err(CliError::BadParameter(err_msg).into());
1500        }
1501
1502        let rent_exempt_balance = rpc_client
1503            .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())
1504            .await?;
1505        if lamports < rent_exempt_balance {
1506            return Err(CliError::BadParameter(format!(
1507                "need at least {rent_exempt_balance} lamports for stake account to be rent \
1508                 exempt, provided lamports: {lamports}"
1509            ))
1510            .into());
1511        }
1512
1513        let minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
1514        let minimum_total_lamports = rent_exempt_balance.saturating_add(minimum_delegation);
1515        if lamports < minimum_total_lamports {
1516            eprintln!(
1517                "Warning: need at least {minimum_total_lamports} lamports to delegate this stake \
1518                 account, provided lamports: {lamports}"
1519            );
1520        }
1521
1522        if let Some(nonce_account) = &nonce_account {
1523            let nonce_account =
1524                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1525                    rpc_client,
1526                    nonce_account,
1527                    config.commitment,
1528                )
1529                .await?;
1530            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1531        }
1532    }
1533
1534    let mut tx = Transaction::new_unsigned(message);
1535    if sign_only {
1536        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1537        return_signers_with_config(
1538            &tx,
1539            &config.output_format,
1540            &ReturnSignersConfig {
1541                dump_transaction_message,
1542            },
1543        )
1544    } else {
1545        tx.try_sign(&config.signers, recent_blockhash)?;
1546        let result = rpc_client
1547            .send_and_confirm_transaction_with_spinner_and_config(
1548                &tx,
1549                config.commitment,
1550                config.send_transaction_config,
1551            )
1552            .await;
1553        log_instruction_custom_error::<SystemError>(result, config)
1554    }
1555}
1556
1557#[allow(clippy::too_many_arguments)]
1558pub async fn process_stake_authorize(
1559    rpc_client: &RpcClient,
1560    config: &CliConfig<'_>,
1561    stake_account_pubkey: &Pubkey,
1562    new_authorizations: &[StakeAuthorizationIndexed],
1563    custodian: Option<SignerIndex>,
1564    sign_only: bool,
1565    dump_transaction_message: bool,
1566    blockhash_query: &BlockhashQuery,
1567    nonce_account: Option<Pubkey>,
1568    nonce_authority: SignerIndex,
1569    memo: Option<&String>,
1570    fee_payer: SignerIndex,
1571    no_wait: bool,
1572    compute_unit_price: Option<u64>,
1573) -> ProcessResult {
1574    let mut ixs = Vec::new();
1575    let custodian = custodian.map(|index| config.signers[index]);
1576    let current_stake_account = if !sign_only {
1577        Some(get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment).await?)
1578    } else {
1579        None
1580    };
1581    for StakeAuthorizationIndexed {
1582        authorization_type,
1583        new_authority_pubkey,
1584        authority,
1585        new_authority_signer,
1586    } in new_authorizations.iter()
1587    {
1588        check_unique_pubkeys(
1589            (stake_account_pubkey, "stake_account_pubkey".to_string()),
1590            (new_authority_pubkey, "new_authorized_pubkey".to_string()),
1591        )?;
1592        let authority = config.signers[*authority];
1593        if let Some(current_stake_account) = current_stake_account {
1594            let authorized = match current_stake_account {
1595                StakeStateV2::Stake(Meta { authorized, .. }, ..) => Some(authorized),
1596                StakeStateV2::Initialized(Meta { authorized, .. }) => Some(authorized),
1597                _ => None,
1598            };
1599            if let Some(authorized) = authorized {
1600                match authorization_type {
1601                    StakeAuthorize::Staker => check_current_authority(
1602                        &[authorized.withdrawer, authorized.staker],
1603                        &authority.pubkey(),
1604                    )?,
1605                    StakeAuthorize::Withdrawer => {
1606                        check_current_authority(&[authorized.withdrawer], &authority.pubkey())?;
1607                    }
1608                }
1609            } else {
1610                return Err(CliError::RpcRequestError(format!(
1611                    "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
1612                ))
1613                .into());
1614            }
1615        }
1616        if new_authority_signer.is_some() {
1617            ixs.push(stake_instruction::authorize_checked(
1618                stake_account_pubkey, // stake account to update
1619                &authority.pubkey(),  // currently authorized
1620                new_authority_pubkey, // new stake signer
1621                *authorization_type,  // stake or withdraw
1622                custodian.map(|signer| signer.pubkey()).as_ref(),
1623            ));
1624        } else {
1625            ixs.push(stake_instruction::authorize(
1626                stake_account_pubkey, // stake account to update
1627                &authority.pubkey(),  // currently authorized
1628                new_authority_pubkey, // new stake signer
1629                *authorization_type,  // stake or withdraw
1630                custodian.map(|signer| signer.pubkey()).as_ref(),
1631            ));
1632        }
1633    }
1634    let compute_unit_limit = match blockhash_query {
1635        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1636        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1637    };
1638    ixs = ixs
1639        .with_memo(memo)
1640        .with_compute_unit_config(&ComputeUnitConfig {
1641            compute_unit_price,
1642            compute_unit_limit,
1643        });
1644
1645    let recent_blockhash = blockhash_query
1646        .get_blockhash(rpc_client, config.commitment)
1647        .await?;
1648
1649    let nonce_authority = config.signers[nonce_authority];
1650    let fee_payer = config.signers[fee_payer];
1651
1652    let mut message = if let Some(nonce_account) = &nonce_account {
1653        Message::new_with_nonce(
1654            ixs,
1655            Some(&fee_payer.pubkey()),
1656            nonce_account,
1657            &nonce_authority.pubkey(),
1658        )
1659    } else {
1660        Message::new(&ixs, Some(&fee_payer.pubkey()))
1661    };
1662    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1663    let mut tx = Transaction::new_unsigned(message);
1664
1665    if sign_only {
1666        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1667        return_signers_with_config(
1668            &tx,
1669            &config.output_format,
1670            &ReturnSignersConfig {
1671                dump_transaction_message,
1672            },
1673        )
1674    } else {
1675        tx.try_sign(&config.signers, recent_blockhash)?;
1676        if let Some(nonce_account) = &nonce_account {
1677            let nonce_account =
1678                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1679                    rpc_client,
1680                    nonce_account,
1681                    config.commitment,
1682                )
1683                .await?;
1684            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1685        }
1686        check_account_for_fee_with_commitment(
1687            rpc_client,
1688            &tx.message.account_keys[0],
1689            &tx.message,
1690            config.commitment,
1691        )
1692        .await?;
1693        let result = if no_wait {
1694            rpc_client
1695                .send_transaction_with_config(&tx, config.send_transaction_config)
1696                .await
1697        } else {
1698            rpc_client
1699                .send_and_confirm_transaction_with_spinner_and_config(
1700                    &tx,
1701                    config.commitment,
1702                    config.send_transaction_config,
1703                )
1704                .await
1705        };
1706        log_instruction_custom_error::<StakeError>(result, config)
1707    }
1708}
1709
1710#[allow(clippy::too_many_arguments)]
1711pub async fn process_deactivate_stake_account(
1712    rpc_client: &RpcClient,
1713    config: &CliConfig<'_>,
1714    stake_account_pubkey: &Pubkey,
1715    stake_authority: SignerIndex,
1716    sign_only: bool,
1717    deactivate_delinquent: bool,
1718    dump_transaction_message: bool,
1719    blockhash_query: &BlockhashQuery,
1720    nonce_account: Option<Pubkey>,
1721    nonce_authority: SignerIndex,
1722    memo: Option<&String>,
1723    seed: Option<&String>,
1724    fee_payer: SignerIndex,
1725    compute_unit_price: Option<u64>,
1726) -> ProcessResult {
1727    let recent_blockhash = blockhash_query
1728        .get_blockhash(rpc_client, config.commitment)
1729        .await?;
1730
1731    let stake_account_address = if let Some(seed) = seed {
1732        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1733    } else {
1734        *stake_account_pubkey
1735    };
1736
1737    // DeactivateDelinquent parses a VoteState, which may change between simulation and execution
1738    let compute_unit_limit = match blockhash_query {
1739        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1740        BlockhashQuery::Rpc(_) if deactivate_delinquent => {
1741            ComputeUnitLimit::SimulatedWithExtraPercentage(5)
1742        }
1743        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1744    };
1745    let ixs = vec![if deactivate_delinquent {
1746        let stake_account = rpc_client.get_account(&stake_account_address).await?;
1747        if stake_account.owner != stake::program::id() {
1748            return Err(CliError::BadParameter(format!(
1749                "{stake_account_address} is not a stake account",
1750            ))
1751            .into());
1752        }
1753
1754        let vote_account_address = match stake_account.state() {
1755            Ok(stake_state) => match stake_state {
1756                StakeStateV2::Stake(_, stake, _) => stake.delegation.voter_pubkey,
1757                _ => {
1758                    return Err(CliError::BadParameter(format!(
1759                        "{stake_account_address} is not a delegated stake account",
1760                    ))
1761                    .into());
1762                }
1763            },
1764            Err(err) => {
1765                return Err(CliError::RpcRequestError(format!(
1766                    "Account data could not be deserialized to stake state: {err}"
1767                ))
1768                .into());
1769            }
1770        };
1771
1772        let current_epoch = rpc_client.get_epoch_info().await?.epoch;
1773
1774        let (_, vote_state) = crate::vote::get_vote_account(
1775            rpc_client,
1776            &vote_account_address,
1777            rpc_client.commitment(),
1778        )
1779        .await?;
1780        if !eligible_for_deactivate_delinquent(&vote_state.epoch_credits, current_epoch) {
1781            return Err(CliError::BadParameter(format!(
1782                "Stake has not been delinquent for {} epochs",
1783                stake::MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION,
1784            ))
1785            .into());
1786        }
1787
1788        // Search for a reference vote account
1789        let reference_vote_account_address = rpc_client
1790            .get_vote_accounts()
1791            .await?
1792            .current
1793            .into_iter()
1794            .find(|vote_account_info| {
1795                acceptable_reference_epoch_credits(&vote_account_info.epoch_credits, current_epoch)
1796            });
1797        let reference_vote_account_address = reference_vote_account_address
1798            .ok_or_else(|| {
1799                CliError::RpcRequestError("Unable to find a reference vote account".into())
1800            })?
1801            .vote_pubkey
1802            .parse()?;
1803
1804        stake_instruction::deactivate_delinquent_stake(
1805            &stake_account_address,
1806            &vote_account_address,
1807            &reference_vote_account_address,
1808        )
1809    } else {
1810        let stake_authority = config.signers[stake_authority];
1811        stake_instruction::deactivate_stake(&stake_account_address, &stake_authority.pubkey())
1812    }]
1813    .with_memo(memo)
1814    .with_compute_unit_config(&ComputeUnitConfig {
1815        compute_unit_price,
1816        compute_unit_limit,
1817    });
1818
1819    let nonce_authority = config.signers[nonce_authority];
1820    let fee_payer = config.signers[fee_payer];
1821
1822    let mut message = if let Some(nonce_account) = &nonce_account {
1823        Message::new_with_nonce(
1824            ixs,
1825            Some(&fee_payer.pubkey()),
1826            nonce_account,
1827            &nonce_authority.pubkey(),
1828        )
1829    } else {
1830        Message::new(&ixs, Some(&fee_payer.pubkey()))
1831    };
1832    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1833    let mut tx = Transaction::new_unsigned(message);
1834
1835    if sign_only {
1836        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1837        return_signers_with_config(
1838            &tx,
1839            &config.output_format,
1840            &ReturnSignersConfig {
1841                dump_transaction_message,
1842            },
1843        )
1844    } else {
1845        tx.try_sign(&config.signers, recent_blockhash)?;
1846        if let Some(nonce_account) = &nonce_account {
1847            let nonce_account =
1848                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1849                    rpc_client,
1850                    nonce_account,
1851                    config.commitment,
1852                )
1853                .await?;
1854            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1855        }
1856        check_account_for_fee_with_commitment(
1857            rpc_client,
1858            &tx.message.account_keys[0],
1859            &tx.message,
1860            config.commitment,
1861        )
1862        .await?;
1863        let result = rpc_client
1864            .send_and_confirm_transaction_with_spinner_and_config(
1865                &tx,
1866                config.commitment,
1867                config.send_transaction_config,
1868            )
1869            .await;
1870        log_instruction_custom_error::<StakeError>(result, config)
1871    }
1872}
1873
1874#[allow(clippy::too_many_arguments)]
1875pub async fn process_withdraw_stake(
1876    rpc_client: &RpcClient,
1877    config: &CliConfig<'_>,
1878    stake_account_pubkey: &Pubkey,
1879    destination_account_pubkey: &Pubkey,
1880    amount: SpendAmount,
1881    withdraw_authority: SignerIndex,
1882    custodian: Option<SignerIndex>,
1883    sign_only: bool,
1884    dump_transaction_message: bool,
1885    blockhash_query: &BlockhashQuery,
1886    nonce_account: Option<&Pubkey>,
1887    nonce_authority: SignerIndex,
1888    memo: Option<&String>,
1889    seed: Option<&String>,
1890    fee_payer: SignerIndex,
1891    compute_unit_price: Option<u64>,
1892) -> ProcessResult {
1893    let withdraw_authority = config.signers[withdraw_authority];
1894    let custodian = custodian.map(|index| config.signers[index]);
1895
1896    let stake_account_address = if let Some(seed) = seed {
1897        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1898    } else {
1899        *stake_account_pubkey
1900    };
1901
1902    let recent_blockhash = blockhash_query
1903        .get_blockhash(rpc_client, config.commitment)
1904        .await?;
1905
1906    let fee_payer = config.signers[fee_payer];
1907    let nonce_authority = config.signers[nonce_authority];
1908
1909    let compute_unit_limit = match blockhash_query {
1910        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1911        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1912    };
1913    let build_message = |lamports| {
1914        let ixs = vec![stake_instruction::withdraw(
1915            &stake_account_address,
1916            &withdraw_authority.pubkey(),
1917            destination_account_pubkey,
1918            lamports,
1919            custodian.map(|signer| signer.pubkey()).as_ref(),
1920        )]
1921        .with_memo(memo)
1922        .with_compute_unit_config(&ComputeUnitConfig {
1923            compute_unit_price,
1924            compute_unit_limit,
1925        });
1926
1927        if let Some(nonce_account) = &nonce_account {
1928            Message::new_with_nonce(
1929                ixs,
1930                Some(&fee_payer.pubkey()),
1931                nonce_account,
1932                &nonce_authority.pubkey(),
1933            )
1934        } else {
1935            Message::new(&ixs, Some(&fee_payer.pubkey()))
1936        }
1937    };
1938
1939    let (message, _) = resolve_spend_tx_and_check_account_balances(
1940        rpc_client,
1941        sign_only,
1942        amount,
1943        &recent_blockhash,
1944        &stake_account_address,
1945        &fee_payer.pubkey(),
1946        compute_unit_limit,
1947        build_message,
1948        config.commitment,
1949    )
1950    .await?;
1951
1952    let mut tx = Transaction::new_unsigned(message);
1953
1954    if sign_only {
1955        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1956        return_signers_with_config(
1957            &tx,
1958            &config.output_format,
1959            &ReturnSignersConfig {
1960                dump_transaction_message,
1961            },
1962        )
1963    } else {
1964        tx.try_sign(&config.signers, recent_blockhash)?;
1965        if let Some(nonce_account) = &nonce_account {
1966            let nonce_account =
1967                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1968                    rpc_client,
1969                    nonce_account,
1970                    config.commitment,
1971                )
1972                .await?;
1973            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1974        }
1975        check_account_for_fee_with_commitment(
1976            rpc_client,
1977            &tx.message.account_keys[0],
1978            &tx.message,
1979            config.commitment,
1980        )
1981        .await?;
1982        let result = rpc_client
1983            .send_and_confirm_transaction_with_spinner_and_config(
1984                &tx,
1985                config.commitment,
1986                config.send_transaction_config,
1987            )
1988            .await;
1989        log_instruction_custom_error::<StakeError>(result, config)
1990    }
1991}
1992
1993#[allow(clippy::too_many_arguments)]
1994pub async fn process_split_stake(
1995    rpc_client: &RpcClient,
1996    config: &CliConfig<'_>,
1997    stake_account_pubkey: &Pubkey,
1998    stake_authority: SignerIndex,
1999    sign_only: bool,
2000    dump_transaction_message: bool,
2001    blockhash_query: &BlockhashQuery,
2002    nonce_account: Option<Pubkey>,
2003    nonce_authority: SignerIndex,
2004    memo: Option<&String>,
2005    split_stake_account: SignerIndex,
2006    split_stake_account_seed: &Option<String>,
2007    lamports: u64,
2008    fee_payer: SignerIndex,
2009    compute_unit_price: Option<u64>,
2010    rent_exempt_reserve: Option<&u64>,
2011) -> ProcessResult {
2012    let split_stake_account = config.signers[split_stake_account];
2013    let fee_payer = config.signers[fee_payer];
2014
2015    if split_stake_account_seed.is_none() {
2016        check_unique_pubkeys(
2017            (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2018            (
2019                &split_stake_account.pubkey(),
2020                "split_stake_account".to_string(),
2021            ),
2022        )?;
2023    }
2024    check_unique_pubkeys(
2025        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2026        (stake_account_pubkey, "stake_account".to_string()),
2027    )?;
2028    check_unique_pubkeys(
2029        (stake_account_pubkey, "stake_account".to_string()),
2030        (
2031            &split_stake_account.pubkey(),
2032            "split_stake_account".to_string(),
2033        ),
2034    )?;
2035
2036    let stake_authority = config.signers[stake_authority];
2037
2038    let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
2039        Pubkey::create_with_seed(&split_stake_account.pubkey(), seed, &stake::program::id())?
2040    } else {
2041        split_stake_account.pubkey()
2042    };
2043
2044    let rent_exempt_reserve = if let Some(rent_exempt_reserve) = rent_exempt_reserve {
2045        *rent_exempt_reserve
2046    } else {
2047        let stake_minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2048        if lamports < stake_minimum_delegation {
2049            let lamports = Sol(lamports);
2050            let stake_minimum_delegation = Sol(stake_minimum_delegation);
2051            return Err(CliError::BadParameter(format!(
2052                "need at least {stake_minimum_delegation} for minimum stake delegation, provided: \
2053                 {lamports}"
2054            ))
2055            .into());
2056        }
2057
2058        let check_stake_account = |account: Account| -> Result<u64, CliError> {
2059            match account.owner {
2060                owner if owner == stake::program::id() => Err(CliError::BadParameter(format!(
2061                    "Stake account {split_stake_account_address} already exists"
2062                ))),
2063                owner if owner == system_program::id() => {
2064                    if !account.data.is_empty() {
2065                        Err(CliError::BadParameter(format!(
2066                            "Account {split_stake_account_address} has data and cannot be used to \
2067                             split stake"
2068                        )))
2069                    } else {
2070                        // if `stake_account`'s owner is the system_program and its data is
2071                        // empty, `stake_account` is allowed to receive the stake split
2072                        Ok(account.lamports)
2073                    }
2074                }
2075                _ => Err(CliError::BadParameter(format!(
2076                    "Account {split_stake_account_address} already exists and cannot be used to \
2077                     split stake"
2078                ))),
2079            }
2080        };
2081        let current_balance =
2082            if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address).await {
2083                check_stake_account(stake_account)?
2084            } else {
2085                0
2086            };
2087
2088        let rent_exempt_reserve = rpc_client
2089            .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())
2090            .await?;
2091
2092        rent_exempt_reserve.saturating_sub(current_balance)
2093    };
2094
2095    let recent_blockhash = blockhash_query
2096        .get_blockhash(rpc_client, config.commitment)
2097        .await?;
2098
2099    let mut ixs = vec![];
2100    if rent_exempt_reserve > 0 {
2101        ixs.push(system_instruction::transfer(
2102            &fee_payer.pubkey(),
2103            &split_stake_account_address,
2104            rent_exempt_reserve,
2105        ));
2106    }
2107    let compute_unit_limit = match blockhash_query {
2108        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2109        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2110    };
2111    if let Some(seed) = split_stake_account_seed {
2112        ixs.append(
2113            &mut stake_instruction::split_with_seed(
2114                stake_account_pubkey,
2115                &stake_authority.pubkey(),
2116                lamports,
2117                &split_stake_account_address,
2118                &split_stake_account.pubkey(),
2119                seed,
2120            )
2121            .with_memo(memo)
2122            .with_compute_unit_config(&ComputeUnitConfig {
2123                compute_unit_price,
2124                compute_unit_limit,
2125            }),
2126        )
2127    } else {
2128        ixs.append(
2129            &mut stake_instruction::split(
2130                stake_account_pubkey,
2131                &stake_authority.pubkey(),
2132                lamports,
2133                &split_stake_account_address,
2134            )
2135            .with_memo(memo)
2136            .with_compute_unit_config(&ComputeUnitConfig {
2137                compute_unit_price,
2138                compute_unit_limit,
2139            }),
2140        )
2141    };
2142
2143    let nonce_authority = config.signers[nonce_authority];
2144
2145    let mut message = if let Some(nonce_account) = &nonce_account {
2146        Message::new_with_nonce(
2147            ixs,
2148            Some(&fee_payer.pubkey()),
2149            nonce_account,
2150            &nonce_authority.pubkey(),
2151        )
2152    } else {
2153        Message::new(&ixs, Some(&fee_payer.pubkey()))
2154    };
2155    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2156    let mut tx = Transaction::new_unsigned(message);
2157
2158    if sign_only {
2159        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2160        return_signers_with_config(
2161            &tx,
2162            &config.output_format,
2163            &ReturnSignersConfig {
2164                dump_transaction_message,
2165            },
2166        )
2167    } else {
2168        tx.try_sign(&config.signers, recent_blockhash)?;
2169        if let Some(nonce_account) = &nonce_account {
2170            let nonce_account =
2171                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2172                    rpc_client,
2173                    nonce_account,
2174                    config.commitment,
2175                )
2176                .await?;
2177            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2178        }
2179        check_account_for_fee_with_commitment(
2180            rpc_client,
2181            &tx.message.account_keys[0],
2182            &tx.message,
2183            config.commitment,
2184        )
2185        .await?;
2186        let result = rpc_client
2187            .send_and_confirm_transaction_with_spinner_and_config(
2188                &tx,
2189                config.commitment,
2190                config.send_transaction_config,
2191            )
2192            .await;
2193        log_instruction_custom_error::<StakeError>(result, config)
2194    }
2195}
2196
2197#[allow(clippy::too_many_arguments)]
2198pub async fn process_merge_stake(
2199    rpc_client: &RpcClient,
2200    config: &CliConfig<'_>,
2201    stake_account_pubkey: &Pubkey,
2202    source_stake_account_pubkey: &Pubkey,
2203    stake_authority: SignerIndex,
2204    sign_only: bool,
2205    dump_transaction_message: bool,
2206    blockhash_query: &BlockhashQuery,
2207    nonce_account: Option<Pubkey>,
2208    nonce_authority: SignerIndex,
2209    memo: Option<&String>,
2210    fee_payer: SignerIndex,
2211    compute_unit_price: Option<u64>,
2212) -> ProcessResult {
2213    let fee_payer = config.signers[fee_payer];
2214
2215    check_unique_pubkeys(
2216        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2217        (stake_account_pubkey, "stake_account".to_string()),
2218    )?;
2219    check_unique_pubkeys(
2220        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2221        (
2222            source_stake_account_pubkey,
2223            "source_stake_account".to_string(),
2224        ),
2225    )?;
2226    check_unique_pubkeys(
2227        (stake_account_pubkey, "stake_account".to_string()),
2228        (
2229            source_stake_account_pubkey,
2230            "source_stake_account".to_string(),
2231        ),
2232    )?;
2233
2234    let stake_authority = config.signers[stake_authority];
2235
2236    if !sign_only {
2237        for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] {
2238            if let Ok(stake_account) = rpc_client.get_account(stake_account_address).await {
2239                if stake_account.owner != stake::program::id() {
2240                    return Err(CliError::BadParameter(format!(
2241                        "Account {stake_account_address} is not a stake account"
2242                    ))
2243                    .into());
2244                }
2245            }
2246        }
2247    }
2248
2249    let recent_blockhash = blockhash_query
2250        .get_blockhash(rpc_client, config.commitment)
2251        .await?;
2252
2253    let compute_unit_limit = match blockhash_query {
2254        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2255        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2256    };
2257    let ixs = stake_instruction::merge(
2258        stake_account_pubkey,
2259        source_stake_account_pubkey,
2260        &stake_authority.pubkey(),
2261    )
2262    .with_memo(memo)
2263    .with_compute_unit_config(&ComputeUnitConfig {
2264        compute_unit_price,
2265        compute_unit_limit,
2266    });
2267
2268    let nonce_authority = config.signers[nonce_authority];
2269
2270    let mut message = if let Some(nonce_account) = &nonce_account {
2271        Message::new_with_nonce(
2272            ixs,
2273            Some(&fee_payer.pubkey()),
2274            nonce_account,
2275            &nonce_authority.pubkey(),
2276        )
2277    } else {
2278        Message::new(&ixs, Some(&fee_payer.pubkey()))
2279    };
2280    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2281    let mut tx = Transaction::new_unsigned(message);
2282
2283    if sign_only {
2284        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2285        return_signers_with_config(
2286            &tx,
2287            &config.output_format,
2288            &ReturnSignersConfig {
2289                dump_transaction_message,
2290            },
2291        )
2292    } else {
2293        tx.try_sign(&config.signers, recent_blockhash)?;
2294        if let Some(nonce_account) = &nonce_account {
2295            let nonce_account =
2296                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2297                    rpc_client,
2298                    nonce_account,
2299                    config.commitment,
2300                )
2301                .await?;
2302            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2303        }
2304        check_account_for_fee_with_commitment(
2305            rpc_client,
2306            &tx.message.account_keys[0],
2307            &tx.message,
2308            config.commitment,
2309        )
2310        .await?;
2311        let result = rpc_client
2312            .send_and_confirm_transaction_with_spinner_and_config(
2313                &tx,
2314                config.commitment,
2315                config.send_transaction_config,
2316            )
2317            .await;
2318        log_instruction_custom_error::<StakeError>(result, config)
2319    }
2320}
2321
2322#[allow(clippy::too_many_arguments)]
2323pub async fn process_stake_set_lockup(
2324    rpc_client: &RpcClient,
2325    config: &CliConfig<'_>,
2326    stake_account_pubkey: &Pubkey,
2327    lockup: &LockupArgs,
2328    new_custodian_signer: Option<SignerIndex>,
2329    custodian: SignerIndex,
2330    sign_only: bool,
2331    dump_transaction_message: bool,
2332    blockhash_query: &BlockhashQuery,
2333    nonce_account: Option<Pubkey>,
2334    nonce_authority: SignerIndex,
2335    memo: Option<&String>,
2336    fee_payer: SignerIndex,
2337    compute_unit_price: Option<u64>,
2338) -> ProcessResult {
2339    let recent_blockhash = blockhash_query
2340        .get_blockhash(rpc_client, config.commitment)
2341        .await?;
2342    let custodian = config.signers[custodian];
2343
2344    let compute_unit_limit = match blockhash_query {
2345        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2346        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2347    };
2348    let ixs = vec![if new_custodian_signer.is_some() {
2349        stake_instruction::set_lockup_checked(stake_account_pubkey, lockup, &custodian.pubkey())
2350    } else {
2351        stake_instruction::set_lockup(stake_account_pubkey, lockup, &custodian.pubkey())
2352    }]
2353    .with_memo(memo)
2354    .with_compute_unit_config(&ComputeUnitConfig {
2355        compute_unit_price,
2356        compute_unit_limit,
2357    });
2358    let nonce_authority = config.signers[nonce_authority];
2359    let fee_payer = config.signers[fee_payer];
2360
2361    if !sign_only {
2362        let state =
2363            get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment).await?;
2364        let lockup = match state {
2365            StakeStateV2::Stake(Meta { lockup, .. }, ..) => Some(lockup),
2366            StakeStateV2::Initialized(Meta { lockup, .. }) => Some(lockup),
2367            _ => None,
2368        };
2369        if let Some(lockup) = lockup {
2370            if lockup.custodian != Pubkey::default() {
2371                check_current_authority(&[lockup.custodian], &custodian.pubkey())?;
2372            }
2373        } else {
2374            return Err(CliError::RpcRequestError(format!(
2375                "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
2376            ))
2377            .into());
2378        }
2379    }
2380
2381    let mut message = if let Some(nonce_account) = &nonce_account {
2382        Message::new_with_nonce(
2383            ixs,
2384            Some(&fee_payer.pubkey()),
2385            nonce_account,
2386            &nonce_authority.pubkey(),
2387        )
2388    } else {
2389        Message::new(&ixs, Some(&fee_payer.pubkey()))
2390    };
2391    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2392    let mut tx = Transaction::new_unsigned(message);
2393
2394    if sign_only {
2395        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2396        return_signers_with_config(
2397            &tx,
2398            &config.output_format,
2399            &ReturnSignersConfig {
2400                dump_transaction_message,
2401            },
2402        )
2403    } else {
2404        tx.try_sign(&config.signers, recent_blockhash)?;
2405        if let Some(nonce_account) = &nonce_account {
2406            let nonce_account =
2407                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2408                    rpc_client,
2409                    nonce_account,
2410                    config.commitment,
2411                )
2412                .await?;
2413            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2414        }
2415        check_account_for_fee_with_commitment(
2416            rpc_client,
2417            &tx.message.account_keys[0],
2418            &tx.message,
2419            config.commitment,
2420        )
2421        .await?;
2422        let result = rpc_client
2423            .send_and_confirm_transaction_with_spinner_and_config(
2424                &tx,
2425                config.commitment,
2426                config.send_transaction_config,
2427            )
2428            .await;
2429        log_instruction_custom_error::<StakeError>(result, config)
2430    }
2431}
2432
2433fn u64_some_if_not_zero(n: u64) -> Option<u64> {
2434    if n > 0 { Some(n) } else { None }
2435}
2436
2437pub fn build_stake_state(
2438    account_balance: u64,
2439    stake_state: &StakeStateV2,
2440    use_lamports_unit: bool,
2441    stake_history: &StakeHistory,
2442    clock: &Clock,
2443    new_rate_activation_epoch: Option<Epoch>,
2444    rent_exempt_reserve: u64,
2445    use_csv: bool,
2446) -> CliStakeState {
2447    match stake_state {
2448        StakeStateV2::Stake(
2449            Meta {
2450                #[expect(deprecated)]
2451                    rent_exempt_reserve: _,
2452                authorized,
2453                lockup,
2454            },
2455            stake,
2456            _,
2457        ) => {
2458            let current_epoch = clock.epoch;
2459            let StakeActivationStatus {
2460                effective,
2461                activating,
2462                deactivating,
2463            } = stake.delegation.stake_activating_and_deactivating(
2464                current_epoch,
2465                stake_history,
2466                new_rate_activation_epoch,
2467            );
2468            let lockup = if lockup.is_in_force(clock, None) {
2469                Some(lockup.into())
2470            } else {
2471                None
2472            };
2473            CliStakeState {
2474                stake_type: CliStakeType::Stake,
2475                account_balance,
2476                credits_observed: Some(stake.credits_observed),
2477                delegated_stake: Some(stake.delegation.stake),
2478                delegated_vote_account_address: if stake.delegation.voter_pubkey
2479                    != Pubkey::default()
2480                {
2481                    Some(stake.delegation.voter_pubkey.to_string())
2482                } else {
2483                    None
2484                },
2485                activation_epoch: Some(if stake.delegation.activation_epoch < u64::MAX {
2486                    stake.delegation.activation_epoch
2487                } else {
2488                    0
2489                }),
2490                deactivation_epoch: if stake.delegation.deactivation_epoch < u64::MAX {
2491                    Some(stake.delegation.deactivation_epoch)
2492                } else {
2493                    None
2494                },
2495                authorized: Some(authorized.into()),
2496                lockup,
2497                use_lamports_unit,
2498                current_epoch,
2499                rent_exempt_reserve: Some(rent_exempt_reserve),
2500                active_stake: u64_some_if_not_zero(effective),
2501                activating_stake: u64_some_if_not_zero(activating),
2502                deactivating_stake: u64_some_if_not_zero(deactivating),
2503                use_csv,
2504                ..CliStakeState::default()
2505            }
2506        }
2507        StakeStateV2::RewardsPool => CliStakeState {
2508            stake_type: CliStakeType::RewardsPool,
2509            account_balance,
2510            ..CliStakeState::default()
2511        },
2512        StakeStateV2::Uninitialized => CliStakeState {
2513            account_balance,
2514            ..CliStakeState::default()
2515        },
2516        StakeStateV2::Initialized(Meta {
2517            #[expect(deprecated)]
2518                rent_exempt_reserve: _,
2519            authorized,
2520            lockup,
2521        }) => {
2522            let lockup = if lockup.is_in_force(clock, None) {
2523                Some(lockup.into())
2524            } else {
2525                None
2526            };
2527            CliStakeState {
2528                stake_type: CliStakeType::Initialized,
2529                account_balance,
2530                credits_observed: Some(0),
2531                authorized: Some(authorized.into()),
2532                lockup,
2533                use_lamports_unit,
2534                rent_exempt_reserve: Some(rent_exempt_reserve),
2535                ..CliStakeState::default()
2536            }
2537        }
2538    }
2539}
2540
2541async fn get_stake_account_state(
2542    rpc_client: &RpcClient,
2543    stake_account_pubkey: &Pubkey,
2544    commitment_config: CommitmentConfig,
2545) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
2546    let stake_account = rpc_client
2547        .get_account_with_commitment(stake_account_pubkey, commitment_config)
2548        .await?
2549        .value
2550        .ok_or_else(|| {
2551            CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
2552        })?;
2553    if stake_account.owner != stake::program::id() {
2554        return Err(CliError::RpcRequestError(format!(
2555            "{stake_account_pubkey:?} is not a stake account",
2556        ))
2557        .into());
2558    }
2559    stake_account.state().map_err(|err| {
2560        CliError::RpcRequestError(format!(
2561            "Account data could not be deserialized to stake state: {err}"
2562        ))
2563        .into()
2564    })
2565}
2566
2567pub(crate) fn check_current_authority(
2568    permitted_authorities: &[Pubkey],
2569    provided_current_authority: &Pubkey,
2570) -> Result<(), CliError> {
2571    if !permitted_authorities.contains(provided_current_authority) {
2572        Err(CliError::RpcRequestError(format!(
2573            "Invalid authority provided: {provided_current_authority:?}, expected \
2574             {permitted_authorities:?}"
2575        )))
2576    } else {
2577        Ok(())
2578    }
2579}
2580
2581pub async fn get_epoch_boundary_timestamps(
2582    rpc_client: &RpcClient,
2583    reward: &RpcInflationReward,
2584    epoch_schedule: &EpochSchedule,
2585) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
2586    let epoch_end_time = rpc_client.get_block_time(reward.effective_slot).await?;
2587    let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
2588    let epoch_start_time = loop {
2589        if epoch_start_slot >= reward.effective_slot {
2590            return Err("epoch_start_time not found".to_string().into());
2591        }
2592        match rpc_client.get_block_time(epoch_start_slot).await {
2593            Ok(block_time) => {
2594                break block_time;
2595            }
2596            Err(_) => {
2597                // TODO This is wrong.  We should not just increase the slot index if the RPC
2598                // request failed.  It could have failed for a number of reasons, including, for
2599                // example a network failure.
2600                epoch_start_slot = epoch_start_slot
2601                    .checked_add(1)
2602                    .ok_or("Reached last slot that fits into u64")?;
2603            }
2604        }
2605    };
2606    Ok((epoch_start_time, epoch_end_time))
2607}
2608
2609pub fn make_cli_reward(
2610    reward: &RpcInflationReward,
2611    block_time: UnixTimestamp,
2612    epoch_start_time: UnixTimestamp,
2613    epoch_end_time: UnixTimestamp,
2614) -> Option<CliEpochReward> {
2615    let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
2616    if reward.post_balance > reward.amount {
2617        let rate_change =
2618            reward.amount as f64 / (reward.post_balance.saturating_sub(reward.amount)) as f64;
2619
2620        let wallclock_epochs_per_year =
2621            (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
2622        let apr = rate_change * wallclock_epochs_per_year;
2623
2624        Some(CliEpochReward {
2625            epoch: reward.epoch,
2626            effective_slot: reward.effective_slot,
2627            amount: reward.amount,
2628            post_balance: reward.post_balance,
2629            percent_change: rate_change * 100.0,
2630            apr: Some(apr * 100.0),
2631            commission: reward.commission,
2632            block_time,
2633        })
2634    } else {
2635        None
2636    }
2637}
2638
2639pub(crate) async fn fetch_epoch_rewards(
2640    rpc_client: &RpcClient,
2641    address: &Pubkey,
2642    mut num_epochs: usize,
2643    starting_epoch: Option<u64>,
2644) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
2645    let mut all_epoch_rewards = vec![];
2646    let epoch_schedule = rpc_client.get_epoch_schedule().await?;
2647    let mut rewards_epoch = if let Some(epoch) = starting_epoch {
2648        epoch
2649    } else {
2650        rpc_client
2651            .get_epoch_info()
2652            .await?
2653            .epoch
2654            .saturating_sub(num_epochs as u64)
2655    };
2656
2657    while num_epochs > 0 {
2658        if let Ok(rewards) = rpc_client
2659            .get_inflation_reward(&[*address], Some(rewards_epoch))
2660            .await
2661        {
2662            if let Some(reward) = &rewards[0] {
2663                let (epoch_start_time, epoch_end_time) =
2664                    get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule).await?;
2665                let block_time = rpc_client.get_block_time(reward.effective_slot).await?;
2666                if let Some(cli_reward) =
2667                    make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
2668                {
2669                    all_epoch_rewards.push(cli_reward);
2670                }
2671            }
2672        } else {
2673            eprintln!("Rewards not available for epoch {rewards_epoch}");
2674        }
2675        num_epochs = num_epochs.saturating_sub(1);
2676        rewards_epoch = rewards_epoch.saturating_add(1);
2677    }
2678
2679    Ok(all_epoch_rewards)
2680}
2681
2682pub async fn process_show_stake_account(
2683    rpc_client: &RpcClient,
2684    config: &CliConfig<'_>,
2685    stake_account_address: &Pubkey,
2686    use_lamports_unit: bool,
2687    with_rewards: Option<usize>,
2688    use_csv: bool,
2689    starting_epoch: Option<u64>,
2690) -> ProcessResult {
2691    let stake_account = rpc_client.get_account(stake_account_address).await?;
2692    let state = get_account_stake_state(
2693        rpc_client,
2694        stake_account_address,
2695        stake_account,
2696        use_lamports_unit,
2697        with_rewards,
2698        use_csv,
2699        starting_epoch,
2700    )
2701    .await?;
2702    Ok(config.output_format.formatted_string(&state))
2703}
2704
2705pub async fn get_account_stake_state(
2706    rpc_client: &RpcClient,
2707    stake_account_address: &Pubkey,
2708    stake_account: solana_account::Account,
2709    use_lamports_unit: bool,
2710    with_rewards: Option<usize>,
2711    use_csv: bool,
2712    starting_epoch: Option<u64>,
2713) -> Result<CliStakeState, CliError> {
2714    if stake_account.owner != stake::program::id() {
2715        return Err(CliError::RpcRequestError(format!(
2716            "{stake_account_address:?} is not a stake account",
2717        )));
2718    }
2719    match stake_account.state() {
2720        Ok(stake_state) => {
2721            let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2722            let stake_history = from_account(&stake_history_account).ok_or_else(|| {
2723                CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2724            })?;
2725            let clock_account = rpc_client.get_account(&clock::id()).await?;
2726            let clock: Clock = from_account(&clock_account).ok_or_else(|| {
2727                CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
2728            })?;
2729            let new_rate_activation_epoch = get_feature_activation_epoch(
2730                rpc_client,
2731                &agave_feature_set::reduce_stake_warmup_cooldown::id(),
2732            )
2733            .await?;
2734            let rent_exempt_balance = rpc_client
2735                .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2736                .await?;
2737            let mut state = build_stake_state(
2738                stake_account.lamports,
2739                &stake_state,
2740                use_lamports_unit,
2741                &stake_history,
2742                &clock,
2743                new_rate_activation_epoch,
2744                rent_exempt_balance,
2745                use_csv,
2746            );
2747
2748            if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() {
2749                if let Some(num_epochs) = with_rewards {
2750                    state.epoch_rewards = match fetch_epoch_rewards(
2751                        rpc_client,
2752                        stake_account_address,
2753                        num_epochs,
2754                        starting_epoch,
2755                    )
2756                    .await
2757                    {
2758                        Ok(rewards) => Some(rewards),
2759                        Err(error) => {
2760                            eprintln!("Failed to fetch epoch rewards: {error:?}");
2761                            None
2762                        }
2763                    };
2764                }
2765            }
2766            Ok(state)
2767        }
2768        Err(err) => Err(CliError::RpcRequestError(format!(
2769            "Account data could not be deserialized to stake state: {err}"
2770        ))),
2771    }
2772}
2773
2774pub async fn process_show_stake_history(
2775    rpc_client: &RpcClient,
2776    config: &CliConfig<'_>,
2777    use_lamports_unit: bool,
2778    limit_results: usize,
2779) -> ProcessResult {
2780    let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2781    let stake_history =
2782        from_account::<StakeHistory, _>(&stake_history_account).ok_or_else(|| {
2783            CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2784        })?;
2785
2786    let limit_results = match config.output_format {
2787        OutputFormat::Json | OutputFormat::JsonCompact => usize::MAX,
2788        _ => {
2789            if limit_results == 0 {
2790                usize::MAX
2791            } else {
2792                limit_results
2793            }
2794        }
2795    };
2796    let mut entries: Vec<CliStakeHistoryEntry> = vec![];
2797    for entry in stake_history.deref().iter().take(limit_results) {
2798        entries.push(entry.into());
2799    }
2800    let stake_history_output = CliStakeHistory {
2801        entries,
2802        use_lamports_unit,
2803    };
2804    Ok(config.output_format.formatted_string(&stake_history_output))
2805}
2806
2807#[allow(clippy::too_many_arguments)]
2808pub async fn process_delegate_stake(
2809    rpc_client: &RpcClient,
2810    config: &CliConfig<'_>,
2811    stake_account_pubkey: &Pubkey,
2812    vote_account_pubkey: &Pubkey,
2813    stake_authority: SignerIndex,
2814    force: bool,
2815    sign_only: bool,
2816    dump_transaction_message: bool,
2817    blockhash_query: &BlockhashQuery,
2818    nonce_account: Option<Pubkey>,
2819    nonce_authority: SignerIndex,
2820    memo: Option<&String>,
2821    fee_payer: SignerIndex,
2822    compute_unit_price: Option<u64>,
2823) -> ProcessResult {
2824    check_unique_pubkeys(
2825        (&config.signers[0].pubkey(), "cli keypair".to_string()),
2826        (stake_account_pubkey, "stake_account_pubkey".to_string()),
2827    )?;
2828    let stake_authority = config.signers[stake_authority];
2829
2830    if !sign_only {
2831        // Sanity check the vote account to ensure it is attached to a validator that has recently
2832        // voted at the tip of the ledger
2833        let get_vote_accounts_config = RpcGetVoteAccountsConfig {
2834            vote_pubkey: Some(vote_account_pubkey.to_string()),
2835            keep_unstaked_delinquents: Some(true),
2836            commitment: Some(rpc_client.commitment()),
2837            ..RpcGetVoteAccountsConfig::default()
2838        };
2839        let RpcVoteAccountStatus {
2840            current,
2841            delinquent,
2842        } = rpc_client
2843            .get_vote_accounts_with_config(get_vote_accounts_config)
2844            .await?;
2845        // filter should return at most one result
2846        let rpc_vote_account =
2847            current
2848                .first()
2849                .or_else(|| delinquent.first())
2850                .ok_or(CliError::RpcRequestError(format!(
2851                    "Vote account not found: {vote_account_pubkey}"
2852                )))?;
2853
2854        let activated_stake = rpc_vote_account.activated_stake;
2855        let root_slot = rpc_vote_account.root_slot;
2856        let min_root_slot = rpc_client
2857            .get_slot()
2858            .await
2859            .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
2860        let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
2861            Ok(())
2862        } else if root_slot == 0 {
2863            Err(CliError::BadParameter(
2864                "Unable to delegate. Vote account has no root slot".to_string(),
2865            ))
2866        } else {
2867            Err(CliError::DynamicProgramError(format!(
2868                "Unable to delegate.  Vote account appears delinquent because its current root \
2869                 slot, {root_slot}, is less than {min_root_slot}"
2870            )))
2871        };
2872
2873        if let Err(err) = &sanity_check_result {
2874            if !force {
2875                sanity_check_result?;
2876            } else {
2877                println!("--force supplied, ignoring: {err}");
2878            }
2879        }
2880
2881        if !force {
2882            let stake_account = rpc_client.get_account(stake_account_pubkey).await?;
2883            if stake_account.owner != stake::program::id() {
2884                return Err(CliError::BadParameter(format!(
2885                    "{stake_account_pubkey} is not a stake account",
2886                ))
2887                .into());
2888            }
2889
2890            let rent_exempt_balance = rpc_client
2891                .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2892                .await?;
2893            let minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2894            let minimum_total_lamports = rent_exempt_balance.saturating_add(minimum_delegation);
2895            let stake_account_lamports = stake_account.lamports;
2896
2897            if stake_account_lamports < minimum_total_lamports {
2898                return Err(CliError::BadParameter(format!(
2899                    "need at least {minimum_total_lamports} lamports to delegate this stake \
2900                     account, available lamports: {stake_account_lamports}"
2901                ))
2902                .into());
2903            }
2904        }
2905    }
2906
2907    let recent_blockhash = blockhash_query
2908        .get_blockhash(rpc_client, config.commitment)
2909        .await?;
2910
2911    // DelegateStake parses a VoteState, which may change between simulation and execution
2912    let compute_unit_limit = match blockhash_query {
2913        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2914        BlockhashQuery::Rpc(_) => ComputeUnitLimit::SimulatedWithExtraPercentage(5),
2915    };
2916    let ixs = vec![stake_instruction::delegate_stake(
2917        stake_account_pubkey,
2918        &stake_authority.pubkey(),
2919        vote_account_pubkey,
2920    )]
2921    .with_memo(memo)
2922    .with_compute_unit_config(&ComputeUnitConfig {
2923        compute_unit_price,
2924        compute_unit_limit,
2925    });
2926
2927    let nonce_authority = config.signers[nonce_authority];
2928    let fee_payer = config.signers[fee_payer];
2929
2930    let mut message = if let Some(nonce_account) = &nonce_account {
2931        Message::new_with_nonce(
2932            ixs,
2933            Some(&fee_payer.pubkey()),
2934            nonce_account,
2935            &nonce_authority.pubkey(),
2936        )
2937    } else {
2938        Message::new(&ixs, Some(&fee_payer.pubkey()))
2939    };
2940    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2941    let mut tx = Transaction::new_unsigned(message);
2942
2943    if sign_only {
2944        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2945        return_signers_with_config(
2946            &tx,
2947            &config.output_format,
2948            &ReturnSignersConfig {
2949                dump_transaction_message,
2950            },
2951        )
2952    } else {
2953        tx.try_sign(&config.signers, recent_blockhash)?;
2954        if let Some(nonce_account) = &nonce_account {
2955            let nonce_account =
2956                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2957                    rpc_client,
2958                    nonce_account,
2959                    config.commitment,
2960                )
2961                .await?;
2962            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2963        }
2964        check_account_for_fee_with_commitment(
2965            rpc_client,
2966            &tx.message.account_keys[0],
2967            &tx.message,
2968            config.commitment,
2969        )
2970        .await?;
2971        let result = rpc_client
2972            .send_and_confirm_transaction_with_spinner_and_config(
2973                &tx,
2974                config.commitment,
2975                config.send_transaction_config,
2976            )
2977            .await;
2978        log_instruction_custom_error::<StakeError>(result, config)
2979    }
2980}
2981
2982pub async fn process_stake_minimum_delegation(
2983    rpc_client: &RpcClient,
2984    config: &CliConfig<'_>,
2985    use_lamports_unit: bool,
2986) -> ProcessResult {
2987    let stake_minimum_delegation = rpc_client
2988        .get_stake_minimum_delegation_with_commitment(config.commitment)
2989        .await?;
2990
2991    let stake_minimum_delegation_output = CliBalance {
2992        lamports: stake_minimum_delegation,
2993        config: BuildBalanceMessageConfig {
2994            use_lamports_unit,
2995            show_unit: true,
2996            trim_trailing_zeros: true,
2997        },
2998    };
2999
3000    Ok(config
3001        .output_format
3002        .formatted_string(&stake_minimum_delegation_output))
3003}
3004
3005#[cfg(test)]
3006mod tests {
3007    use {
3008        super::*,
3009        crate::{clap_app::get_clap_app, cli::parse_command},
3010        solana_hash::Hash,
3011        solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair},
3012        solana_presigner::Presigner,
3013        solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
3014        solana_signer::Signer,
3015        tempfile::NamedTempFile,
3016    };
3017
3018    fn make_tmp_file() -> (String, NamedTempFile) {
3019        let tmp_file = NamedTempFile::new().unwrap();
3020        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
3021    }
3022
3023    #[test]
3024    #[allow(clippy::cognitive_complexity)]
3025    fn test_parse_command() {
3026        let test_commands = get_clap_app("test", "desc", "version");
3027        let default_keypair = Keypair::new();
3028        let (default_keypair_file, mut tmp_file) = make_tmp_file();
3029        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
3030        let default_signer = DefaultSigner::new("", &default_keypair_file);
3031        let (keypair_file, mut tmp_file) = make_tmp_file();
3032        let stake_account_keypair = Keypair::new();
3033        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
3034        let stake_account_pubkey = stake_account_keypair.pubkey();
3035        let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
3036        let stake_authority_keypair = Keypair::new();
3037        write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
3038        let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
3039        let custodian_keypair = Keypair::new();
3040        write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
3041
3042        // stake-authorize subcommand
3043        let stake_account_string = stake_account_pubkey.to_string();
3044        let new_stake_authority = Pubkey::from([1u8; 32]);
3045        let new_stake_string = new_stake_authority.to_string();
3046        let new_withdraw_authority = Pubkey::from([2u8; 32]);
3047        let new_withdraw_string = new_withdraw_authority.to_string();
3048        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3049            "test",
3050            "stake-authorize",
3051            &stake_account_string,
3052            "--new-stake-authority",
3053            &new_stake_string,
3054            "--new-withdraw-authority",
3055            &new_withdraw_string,
3056        ]);
3057        assert_eq!(
3058            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3059            CliCommandInfo {
3060                command: CliCommand::StakeAuthorize {
3061                    stake_account_pubkey,
3062                    new_authorizations: vec![
3063                        StakeAuthorizationIndexed {
3064                            authorization_type: StakeAuthorize::Staker,
3065                            new_authority_pubkey: new_stake_authority,
3066                            authority: 0,
3067                            new_authority_signer: None,
3068                        },
3069                        StakeAuthorizationIndexed {
3070                            authorization_type: StakeAuthorize::Withdrawer,
3071                            new_authority_pubkey: new_withdraw_authority,
3072                            authority: 0,
3073                            new_authority_signer: None,
3074                        },
3075                    ],
3076                    sign_only: false,
3077                    dump_transaction_message: false,
3078                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3079                    nonce_account: None,
3080                    nonce_authority: 0,
3081                    memo: None,
3082                    fee_payer: 0,
3083                    custodian: None,
3084                    no_wait: false,
3085                    compute_unit_price: None,
3086                },
3087                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3088            },
3089        );
3090        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3091        let withdraw_authority_keypair = Keypair::new();
3092        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3093        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3094            "test",
3095            "stake-authorize",
3096            &stake_account_string,
3097            "--new-stake-authority",
3098            &new_stake_string,
3099            "--new-withdraw-authority",
3100            &new_withdraw_string,
3101            "--stake-authority",
3102            &stake_authority_keypair_file,
3103            "--withdraw-authority",
3104            &withdraw_authority_keypair_file,
3105        ]);
3106        assert_eq!(
3107            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3108            CliCommandInfo {
3109                command: CliCommand::StakeAuthorize {
3110                    stake_account_pubkey,
3111                    new_authorizations: vec![
3112                        StakeAuthorizationIndexed {
3113                            authorization_type: StakeAuthorize::Staker,
3114                            new_authority_pubkey: new_stake_authority,
3115                            authority: 1,
3116                            new_authority_signer: None,
3117                        },
3118                        StakeAuthorizationIndexed {
3119                            authorization_type: StakeAuthorize::Withdrawer,
3120                            new_authority_pubkey: new_withdraw_authority,
3121                            authority: 2,
3122                            new_authority_signer: None,
3123                        },
3124                    ],
3125                    sign_only: false,
3126                    dump_transaction_message: false,
3127                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3128                    nonce_account: None,
3129                    nonce_authority: 0,
3130                    memo: None,
3131                    fee_payer: 0,
3132                    custodian: None,
3133                    no_wait: false,
3134                    compute_unit_price: None,
3135                },
3136                signers: vec![
3137                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3138                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3139                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3140                ],
3141            },
3142        );
3143        // Withdraw authority may set both new authorities
3144        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3145            "test",
3146            "stake-authorize",
3147            &stake_account_string,
3148            "--new-stake-authority",
3149            &new_stake_string,
3150            "--new-withdraw-authority",
3151            &new_withdraw_string,
3152            "--withdraw-authority",
3153            &withdraw_authority_keypair_file,
3154        ]);
3155        assert_eq!(
3156            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3157            CliCommandInfo {
3158                command: CliCommand::StakeAuthorize {
3159                    stake_account_pubkey,
3160                    new_authorizations: vec![
3161                        StakeAuthorizationIndexed {
3162                            authorization_type: StakeAuthorize::Staker,
3163                            new_authority_pubkey: new_stake_authority,
3164                            authority: 1,
3165                            new_authority_signer: None,
3166                        },
3167                        StakeAuthorizationIndexed {
3168                            authorization_type: StakeAuthorize::Withdrawer,
3169                            new_authority_pubkey: new_withdraw_authority,
3170                            authority: 1,
3171                            new_authority_signer: None,
3172                        },
3173                    ],
3174                    sign_only: false,
3175                    dump_transaction_message: false,
3176                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3177                    nonce_account: None,
3178                    nonce_authority: 0,
3179                    memo: None,
3180                    fee_payer: 0,
3181                    custodian: None,
3182                    no_wait: false,
3183                    compute_unit_price: None,
3184                },
3185                signers: vec![
3186                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3187                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3188                ],
3189            },
3190        );
3191        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3192            "test",
3193            "stake-authorize",
3194            &stake_account_string,
3195            "--new-stake-authority",
3196            &new_stake_string,
3197        ]);
3198        assert_eq!(
3199            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3200            CliCommandInfo {
3201                command: CliCommand::StakeAuthorize {
3202                    stake_account_pubkey,
3203                    new_authorizations: vec![StakeAuthorizationIndexed {
3204                        authorization_type: StakeAuthorize::Staker,
3205                        new_authority_pubkey: new_stake_authority,
3206                        authority: 0,
3207                        new_authority_signer: None,
3208                    }],
3209                    sign_only: false,
3210                    dump_transaction_message: false,
3211                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3212                    nonce_account: None,
3213                    nonce_authority: 0,
3214                    memo: None,
3215                    fee_payer: 0,
3216                    custodian: None,
3217                    no_wait: false,
3218                    compute_unit_price: None,
3219                },
3220                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3221            },
3222        );
3223        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3224            "test",
3225            "stake-authorize",
3226            &stake_account_string,
3227            "--new-stake-authority",
3228            &new_stake_string,
3229            "--stake-authority",
3230            &stake_authority_keypair_file,
3231        ]);
3232        assert_eq!(
3233            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3234            CliCommandInfo {
3235                command: CliCommand::StakeAuthorize {
3236                    stake_account_pubkey,
3237                    new_authorizations: vec![StakeAuthorizationIndexed {
3238                        authorization_type: StakeAuthorize::Staker,
3239                        new_authority_pubkey: new_stake_authority,
3240                        authority: 1,
3241                        new_authority_signer: None,
3242                    }],
3243                    sign_only: false,
3244                    dump_transaction_message: false,
3245                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3246                    nonce_account: None,
3247                    nonce_authority: 0,
3248                    memo: None,
3249                    fee_payer: 0,
3250                    custodian: None,
3251                    no_wait: false,
3252                    compute_unit_price: None,
3253                },
3254                signers: vec![
3255                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3256                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3257                ],
3258            },
3259        );
3260        // Withdraw authority may set new stake authority
3261        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3262            "test",
3263            "stake-authorize",
3264            &stake_account_string,
3265            "--new-stake-authority",
3266            &new_stake_string,
3267            "--withdraw-authority",
3268            &withdraw_authority_keypair_file,
3269        ]);
3270        assert_eq!(
3271            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3272            CliCommandInfo {
3273                command: CliCommand::StakeAuthorize {
3274                    stake_account_pubkey,
3275                    new_authorizations: vec![StakeAuthorizationIndexed {
3276                        authorization_type: StakeAuthorize::Staker,
3277                        new_authority_pubkey: new_stake_authority,
3278                        authority: 1,
3279                        new_authority_signer: None,
3280                    }],
3281                    sign_only: false,
3282                    dump_transaction_message: false,
3283                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3284                    nonce_account: None,
3285                    nonce_authority: 0,
3286                    memo: None,
3287                    fee_payer: 0,
3288                    custodian: None,
3289                    no_wait: false,
3290                    compute_unit_price: None,
3291                },
3292                signers: vec![
3293                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3294                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3295                ],
3296            },
3297        );
3298        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3299            "test",
3300            "stake-authorize",
3301            &stake_account_string,
3302            "--new-withdraw-authority",
3303            &new_withdraw_string,
3304        ]);
3305        assert_eq!(
3306            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3307            CliCommandInfo {
3308                command: CliCommand::StakeAuthorize {
3309                    stake_account_pubkey,
3310                    new_authorizations: vec![StakeAuthorizationIndexed {
3311                        authorization_type: StakeAuthorize::Withdrawer,
3312                        new_authority_pubkey: new_withdraw_authority,
3313                        authority: 0,
3314                        new_authority_signer: None,
3315                    }],
3316                    sign_only: false,
3317                    dump_transaction_message: false,
3318                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3319                    nonce_account: None,
3320                    nonce_authority: 0,
3321                    memo: None,
3322                    fee_payer: 0,
3323                    custodian: None,
3324                    no_wait: false,
3325                    compute_unit_price: None,
3326                },
3327                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3328            },
3329        );
3330        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3331            "test",
3332            "stake-authorize",
3333            &stake_account_string,
3334            "--new-withdraw-authority",
3335            &new_withdraw_string,
3336            "--withdraw-authority",
3337            &withdraw_authority_keypair_file,
3338        ]);
3339        assert_eq!(
3340            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3341            CliCommandInfo {
3342                command: CliCommand::StakeAuthorize {
3343                    stake_account_pubkey,
3344                    new_authorizations: vec![StakeAuthorizationIndexed {
3345                        authorization_type: StakeAuthorize::Withdrawer,
3346                        new_authority_pubkey: new_withdraw_authority,
3347                        authority: 1,
3348                        new_authority_signer: None,
3349                    }],
3350                    sign_only: false,
3351                    dump_transaction_message: false,
3352                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3353                    nonce_account: None,
3354                    nonce_authority: 0,
3355                    memo: None,
3356                    fee_payer: 0,
3357                    custodian: None,
3358                    no_wait: false,
3359                    compute_unit_price: None,
3360                },
3361                signers: vec![
3362                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3363                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3364                ],
3365            },
3366        );
3367
3368        // Test Authorize Subcommand w/ no-wait
3369        let test_authorize = test_commands.clone().get_matches_from(vec![
3370            "test",
3371            "stake-authorize",
3372            &stake_account_string,
3373            "--new-stake-authority",
3374            &stake_account_string,
3375            "--no-wait",
3376        ]);
3377        assert_eq!(
3378            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3379            CliCommandInfo {
3380                command: CliCommand::StakeAuthorize {
3381                    stake_account_pubkey,
3382                    new_authorizations: vec![StakeAuthorizationIndexed {
3383                        authorization_type: StakeAuthorize::Staker,
3384                        new_authority_pubkey: stake_account_pubkey,
3385                        authority: 0,
3386                        new_authority_signer: None,
3387                    }],
3388                    sign_only: false,
3389                    dump_transaction_message: false,
3390                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3391                    nonce_account: None,
3392                    nonce_authority: 0,
3393                    memo: None,
3394                    fee_payer: 0,
3395                    custodian: None,
3396                    no_wait: true,
3397                    compute_unit_price: None,
3398                },
3399                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3400            }
3401        );
3402
3403        // stake-authorize-checked subcommand
3404        let (authority_keypair_file, mut tmp_file) = make_tmp_file();
3405        let authority_keypair = Keypair::new();
3406        write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
3407        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3408            "test",
3409            "stake-authorize-checked",
3410            &stake_account_string,
3411            "--new-stake-authority",
3412            &authority_keypair_file,
3413            "--new-withdraw-authority",
3414            &authority_keypair_file,
3415        ]);
3416        assert_eq!(
3417            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3418            CliCommandInfo {
3419                command: CliCommand::StakeAuthorize {
3420                    stake_account_pubkey,
3421                    new_authorizations: vec![
3422                        StakeAuthorizationIndexed {
3423                            authorization_type: StakeAuthorize::Staker,
3424                            new_authority_pubkey: authority_keypair.pubkey(),
3425                            authority: 0,
3426                            new_authority_signer: Some(1),
3427                        },
3428                        StakeAuthorizationIndexed {
3429                            authorization_type: StakeAuthorize::Withdrawer,
3430                            new_authority_pubkey: authority_keypair.pubkey(),
3431                            authority: 0,
3432                            new_authority_signer: Some(1),
3433                        },
3434                    ],
3435                    sign_only: false,
3436                    dump_transaction_message: false,
3437                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3438                    nonce_account: None,
3439                    nonce_authority: 0,
3440                    memo: None,
3441                    fee_payer: 0,
3442                    custodian: None,
3443                    no_wait: false,
3444                    compute_unit_price: None,
3445                },
3446                signers: vec![
3447                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3448                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3449                ],
3450            },
3451        );
3452        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3453        let withdraw_authority_keypair = Keypair::new();
3454        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3455        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3456            "test",
3457            "stake-authorize-checked",
3458            &stake_account_string,
3459            "--new-stake-authority",
3460            &authority_keypair_file,
3461            "--new-withdraw-authority",
3462            &authority_keypair_file,
3463            "--stake-authority",
3464            &stake_authority_keypair_file,
3465            "--withdraw-authority",
3466            &withdraw_authority_keypair_file,
3467        ]);
3468        assert_eq!(
3469            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3470            CliCommandInfo {
3471                command: CliCommand::StakeAuthorize {
3472                    stake_account_pubkey,
3473                    new_authorizations: vec![
3474                        StakeAuthorizationIndexed {
3475                            authorization_type: StakeAuthorize::Staker,
3476                            new_authority_pubkey: authority_keypair.pubkey(),
3477                            authority: 1,
3478                            new_authority_signer: Some(2),
3479                        },
3480                        StakeAuthorizationIndexed {
3481                            authorization_type: StakeAuthorize::Withdrawer,
3482                            new_authority_pubkey: authority_keypair.pubkey(),
3483                            authority: 3,
3484                            new_authority_signer: Some(2),
3485                        },
3486                    ],
3487                    sign_only: false,
3488                    dump_transaction_message: false,
3489                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3490                    nonce_account: None,
3491                    nonce_authority: 0,
3492                    memo: None,
3493                    fee_payer: 0,
3494                    custodian: None,
3495                    no_wait: false,
3496                    compute_unit_price: None,
3497                },
3498                signers: vec![
3499                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3500                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3501                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3502                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3503                ],
3504            },
3505        );
3506        // Withdraw authority may set both new authorities
3507        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3508            "test",
3509            "stake-authorize-checked",
3510            &stake_account_string,
3511            "--new-stake-authority",
3512            &authority_keypair_file,
3513            "--new-withdraw-authority",
3514            &authority_keypair_file,
3515            "--withdraw-authority",
3516            &withdraw_authority_keypair_file,
3517        ]);
3518        assert_eq!(
3519            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3520            CliCommandInfo {
3521                command: CliCommand::StakeAuthorize {
3522                    stake_account_pubkey,
3523                    new_authorizations: vec![
3524                        StakeAuthorizationIndexed {
3525                            authorization_type: StakeAuthorize::Staker,
3526                            new_authority_pubkey: authority_keypair.pubkey(),
3527                            authority: 1,
3528                            new_authority_signer: Some(2),
3529                        },
3530                        StakeAuthorizationIndexed {
3531                            authorization_type: StakeAuthorize::Withdrawer,
3532                            new_authority_pubkey: authority_keypair.pubkey(),
3533                            authority: 1,
3534                            new_authority_signer: Some(2),
3535                        },
3536                    ],
3537                    sign_only: false,
3538                    dump_transaction_message: false,
3539                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3540                    nonce_account: None,
3541                    nonce_authority: 0,
3542                    memo: None,
3543                    fee_payer: 0,
3544                    custodian: None,
3545                    no_wait: false,
3546                    compute_unit_price: None,
3547                },
3548                signers: vec![
3549                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3550                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3551                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3552                ],
3553            },
3554        );
3555        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3556            "test",
3557            "stake-authorize-checked",
3558            &stake_account_string,
3559            "--new-stake-authority",
3560            &authority_keypair_file,
3561        ]);
3562        assert_eq!(
3563            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3564            CliCommandInfo {
3565                command: CliCommand::StakeAuthorize {
3566                    stake_account_pubkey,
3567                    new_authorizations: vec![StakeAuthorizationIndexed {
3568                        authorization_type: StakeAuthorize::Staker,
3569                        new_authority_pubkey: authority_keypair.pubkey(),
3570                        authority: 0,
3571                        new_authority_signer: Some(1),
3572                    }],
3573                    sign_only: false,
3574                    dump_transaction_message: false,
3575                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3576                    nonce_account: None,
3577                    nonce_authority: 0,
3578                    memo: None,
3579                    fee_payer: 0,
3580                    custodian: None,
3581                    no_wait: false,
3582                    compute_unit_price: None,
3583                },
3584                signers: vec![
3585                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3586                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3587                ],
3588            },
3589        );
3590        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3591            "test",
3592            "stake-authorize-checked",
3593            &stake_account_string,
3594            "--new-stake-authority",
3595            &authority_keypair_file,
3596            "--stake-authority",
3597            &stake_authority_keypair_file,
3598        ]);
3599        assert_eq!(
3600            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3601            CliCommandInfo {
3602                command: CliCommand::StakeAuthorize {
3603                    stake_account_pubkey,
3604                    new_authorizations: vec![StakeAuthorizationIndexed {
3605                        authorization_type: StakeAuthorize::Staker,
3606                        new_authority_pubkey: authority_keypair.pubkey(),
3607                        authority: 1,
3608                        new_authority_signer: Some(2),
3609                    }],
3610                    sign_only: false,
3611                    dump_transaction_message: false,
3612                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3613                    nonce_account: None,
3614                    nonce_authority: 0,
3615                    memo: None,
3616                    fee_payer: 0,
3617                    custodian: None,
3618                    no_wait: false,
3619                    compute_unit_price: None,
3620                },
3621                signers: vec![
3622                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3623                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3624                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3625                ],
3626            },
3627        );
3628        // Withdraw authority may set new stake authority
3629        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3630            "test",
3631            "stake-authorize-checked",
3632            &stake_account_string,
3633            "--new-stake-authority",
3634            &authority_keypair_file,
3635            "--withdraw-authority",
3636            &withdraw_authority_keypair_file,
3637        ]);
3638        assert_eq!(
3639            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3640            CliCommandInfo {
3641                command: CliCommand::StakeAuthorize {
3642                    stake_account_pubkey,
3643                    new_authorizations: vec![StakeAuthorizationIndexed {
3644                        authorization_type: StakeAuthorize::Staker,
3645                        new_authority_pubkey: authority_keypair.pubkey(),
3646                        authority: 1,
3647                        new_authority_signer: Some(2),
3648                    }],
3649                    sign_only: false,
3650                    dump_transaction_message: false,
3651                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3652                    nonce_account: None,
3653                    nonce_authority: 0,
3654                    memo: None,
3655                    fee_payer: 0,
3656                    custodian: None,
3657                    no_wait: false,
3658                    compute_unit_price: None,
3659                },
3660                signers: vec![
3661                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3662                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3663                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3664                ],
3665            },
3666        );
3667        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3668            "test",
3669            "stake-authorize-checked",
3670            &stake_account_string,
3671            "--new-withdraw-authority",
3672            &authority_keypair_file,
3673        ]);
3674        assert_eq!(
3675            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3676            CliCommandInfo {
3677                command: CliCommand::StakeAuthorize {
3678                    stake_account_pubkey,
3679                    new_authorizations: vec![StakeAuthorizationIndexed {
3680                        authorization_type: StakeAuthorize::Withdrawer,
3681                        new_authority_pubkey: authority_keypair.pubkey(),
3682                        authority: 0,
3683                        new_authority_signer: Some(1),
3684                    }],
3685                    sign_only: false,
3686                    dump_transaction_message: false,
3687                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3688                    nonce_account: None,
3689                    nonce_authority: 0,
3690                    memo: None,
3691                    fee_payer: 0,
3692                    custodian: None,
3693                    no_wait: false,
3694                    compute_unit_price: None,
3695                },
3696                signers: vec![
3697                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3698                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3699                ],
3700            },
3701        );
3702        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3703            "test",
3704            "stake-authorize-checked",
3705            &stake_account_string,
3706            "--new-withdraw-authority",
3707            &authority_keypair_file,
3708            "--withdraw-authority",
3709            &withdraw_authority_keypair_file,
3710        ]);
3711        assert_eq!(
3712            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3713            CliCommandInfo {
3714                command: CliCommand::StakeAuthorize {
3715                    stake_account_pubkey,
3716                    new_authorizations: vec![StakeAuthorizationIndexed {
3717                        authorization_type: StakeAuthorize::Withdrawer,
3718                        new_authority_pubkey: authority_keypair.pubkey(),
3719                        authority: 1,
3720                        new_authority_signer: Some(2),
3721                    }],
3722                    sign_only: false,
3723                    dump_transaction_message: false,
3724                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3725                    nonce_account: None,
3726                    nonce_authority: 0,
3727                    memo: None,
3728                    fee_payer: 0,
3729                    custodian: None,
3730                    no_wait: false,
3731                    compute_unit_price: None,
3732                },
3733                signers: vec![
3734                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3735                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3736                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3737                ],
3738            },
3739        );
3740
3741        // Test Authorize Subcommand w/ no-wait
3742        let test_authorize = test_commands.clone().get_matches_from(vec![
3743            "test",
3744            "stake-authorize-checked",
3745            &stake_account_string,
3746            "--new-stake-authority",
3747            &authority_keypair_file,
3748            "--no-wait",
3749        ]);
3750        assert_eq!(
3751            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3752            CliCommandInfo {
3753                command: CliCommand::StakeAuthorize {
3754                    stake_account_pubkey,
3755                    new_authorizations: vec![StakeAuthorizationIndexed {
3756                        authorization_type: StakeAuthorize::Staker,
3757                        new_authority_pubkey: authority_keypair.pubkey(),
3758                        authority: 0,
3759                        new_authority_signer: Some(1),
3760                    }],
3761                    sign_only: false,
3762                    dump_transaction_message: false,
3763                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3764                    nonce_account: None,
3765                    nonce_authority: 0,
3766                    memo: None,
3767                    fee_payer: 0,
3768                    custodian: None,
3769                    no_wait: true,
3770                    compute_unit_price: None,
3771                },
3772                signers: vec![
3773                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3774                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3775                ],
3776            }
3777        );
3778
3779        // Test Authorize Subcommand w/ sign-only
3780        let blockhash = Hash::default();
3781        let blockhash_string = format!("{blockhash}");
3782        let test_authorize = test_commands.clone().get_matches_from(vec![
3783            "test",
3784            "stake-authorize",
3785            &stake_account_string,
3786            "--new-stake-authority",
3787            &stake_account_string,
3788            "--blockhash",
3789            &blockhash_string,
3790            "--sign-only",
3791        ]);
3792        assert_eq!(
3793            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3794            CliCommandInfo {
3795                command: CliCommand::StakeAuthorize {
3796                    stake_account_pubkey,
3797                    new_authorizations: vec![StakeAuthorizationIndexed {
3798                        authorization_type: StakeAuthorize::Staker,
3799                        new_authority_pubkey: stake_account_pubkey,
3800                        authority: 0,
3801                        new_authority_signer: None,
3802                    }],
3803                    sign_only: true,
3804                    dump_transaction_message: false,
3805                    blockhash_query: BlockhashQuery::Static(blockhash),
3806                    nonce_account: None,
3807                    nonce_authority: 0,
3808                    memo: None,
3809                    fee_payer: 0,
3810                    custodian: None,
3811                    no_wait: false,
3812                    compute_unit_price: None,
3813                },
3814                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3815            }
3816        );
3817        // Test Authorize Subcommand w/ offline feepayer
3818        let keypair = Keypair::new();
3819        let pubkey = keypair.pubkey();
3820        let sig = keypair.sign_message(&[0u8]);
3821        let signer = format!("{}={}", keypair.pubkey(), sig);
3822        let test_authorize = test_commands.clone().get_matches_from(vec![
3823            "test",
3824            "stake-authorize",
3825            &stake_account_string,
3826            "--new-stake-authority",
3827            &stake_account_string,
3828            "--blockhash",
3829            &blockhash_string,
3830            "--signer",
3831            &signer,
3832            "--fee-payer",
3833            &pubkey.to_string(),
3834        ]);
3835        assert_eq!(
3836            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3837            CliCommandInfo {
3838                command: CliCommand::StakeAuthorize {
3839                    stake_account_pubkey,
3840                    new_authorizations: vec![StakeAuthorizationIndexed {
3841                        authorization_type: StakeAuthorize::Staker,
3842                        new_authority_pubkey: stake_account_pubkey,
3843                        authority: 0,
3844                        new_authority_signer: None,
3845                    }],
3846                    sign_only: false,
3847                    dump_transaction_message: false,
3848                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3849                    nonce_account: None,
3850                    nonce_authority: 0,
3851                    memo: None,
3852                    fee_payer: 1,
3853                    custodian: None,
3854                    no_wait: false,
3855                    compute_unit_price: None,
3856                },
3857                signers: vec![
3858                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3859                    Box::new(Presigner::new(&pubkey, &sig))
3860                ],
3861            }
3862        );
3863        // Test Authorize Subcommand w/ offline fee payer and nonce authority
3864        let keypair2 = Keypair::new();
3865        let pubkey2 = keypair2.pubkey();
3866        let sig2 = keypair.sign_message(&[0u8]);
3867        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
3868        let nonce_account = Pubkey::from([1u8; 32]);
3869        let test_authorize = test_commands.clone().get_matches_from(vec![
3870            "test",
3871            "stake-authorize",
3872            &stake_account_string,
3873            "--new-stake-authority",
3874            &stake_account_string,
3875            "--blockhash",
3876            &blockhash_string,
3877            "--signer",
3878            &signer,
3879            "--signer",
3880            &signer2,
3881            "--fee-payer",
3882            &pubkey.to_string(),
3883            "--nonce",
3884            &nonce_account.to_string(),
3885            "--nonce-authority",
3886            &pubkey2.to_string(),
3887        ]);
3888        assert_eq!(
3889            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3890            CliCommandInfo {
3891                command: CliCommand::StakeAuthorize {
3892                    stake_account_pubkey,
3893                    new_authorizations: vec![StakeAuthorizationIndexed {
3894                        authorization_type: StakeAuthorize::Staker,
3895                        new_authority_pubkey: stake_account_pubkey,
3896                        authority: 0,
3897                        new_authority_signer: None,
3898                    }],
3899                    sign_only: false,
3900                    dump_transaction_message: false,
3901                    blockhash_query: BlockhashQuery::Validated(
3902                        Source::NonceAccount(nonce_account),
3903                        blockhash
3904                    ),
3905                    nonce_account: Some(nonce_account),
3906                    nonce_authority: 2,
3907                    memo: None,
3908                    fee_payer: 1,
3909                    custodian: None,
3910                    no_wait: false,
3911                    compute_unit_price: None,
3912                },
3913                signers: vec![
3914                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3915                    Box::new(Presigner::new(&pubkey, &sig)),
3916                    Box::new(Presigner::new(&pubkey2, &sig2)),
3917                ],
3918            }
3919        );
3920        // Test Authorize Subcommand w/ blockhash
3921        let test_authorize = test_commands.clone().get_matches_from(vec![
3922            "test",
3923            "stake-authorize",
3924            &stake_account_string,
3925            "--new-stake-authority",
3926            &stake_account_string,
3927            "--blockhash",
3928            &blockhash_string,
3929        ]);
3930        assert_eq!(
3931            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3932            CliCommandInfo {
3933                command: CliCommand::StakeAuthorize {
3934                    stake_account_pubkey,
3935                    new_authorizations: vec![StakeAuthorizationIndexed {
3936                        authorization_type: StakeAuthorize::Staker,
3937                        new_authority_pubkey: stake_account_pubkey,
3938                        authority: 0,
3939                        new_authority_signer: None,
3940                    }],
3941                    sign_only: false,
3942                    dump_transaction_message: false,
3943                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3944                    nonce_account: None,
3945                    nonce_authority: 0,
3946                    memo: None,
3947                    fee_payer: 0,
3948                    custodian: None,
3949                    no_wait: false,
3950                    compute_unit_price: None,
3951                },
3952                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3953            }
3954        );
3955        // Test Authorize Subcommand w/ nonce
3956        let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
3957        let nonce_authority_keypair = Keypair::new();
3958        write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
3959        let nonce_account_pubkey = nonce_authority_keypair.pubkey();
3960        let nonce_account_string = nonce_account_pubkey.to_string();
3961        let test_authorize = test_commands.clone().get_matches_from(vec![
3962            "test",
3963            "stake-authorize",
3964            &stake_account_string,
3965            "--new-stake-authority",
3966            &stake_account_string,
3967            "--blockhash",
3968            &blockhash_string,
3969            "--nonce",
3970            &nonce_account_string,
3971            "--nonce-authority",
3972            &nonce_keypair_file,
3973        ]);
3974        assert_eq!(
3975            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3976            CliCommandInfo {
3977                command: CliCommand::StakeAuthorize {
3978                    stake_account_pubkey,
3979                    new_authorizations: vec![StakeAuthorizationIndexed {
3980                        authorization_type: StakeAuthorize::Staker,
3981                        new_authority_pubkey: stake_account_pubkey,
3982                        authority: 0,
3983                        new_authority_signer: None,
3984                    }],
3985                    sign_only: false,
3986                    dump_transaction_message: false,
3987                    blockhash_query: BlockhashQuery::Validated(
3988                        Source::NonceAccount(nonce_account_pubkey),
3989                        blockhash
3990                    ),
3991                    nonce_account: Some(nonce_account_pubkey),
3992                    nonce_authority: 1,
3993                    memo: None,
3994                    fee_payer: 0,
3995                    custodian: None,
3996                    no_wait: false,
3997                    compute_unit_price: None,
3998                },
3999                signers: vec![
4000                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4001                    Box::new(nonce_authority_keypair)
4002                ],
4003            }
4004        );
4005        // Test Authorize Subcommand w/ fee-payer
4006        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4007        let fee_payer_keypair = Keypair::new();
4008        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4009        let fee_payer_pubkey = fee_payer_keypair.pubkey();
4010        let fee_payer_string = fee_payer_pubkey.to_string();
4011        let test_authorize = test_commands.clone().get_matches_from(vec![
4012            "test",
4013            "stake-authorize",
4014            &stake_account_string,
4015            "--new-stake-authority",
4016            &stake_account_string,
4017            "--fee-payer",
4018            &fee_payer_keypair_file,
4019        ]);
4020        assert_eq!(
4021            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4022            CliCommandInfo {
4023                command: CliCommand::StakeAuthorize {
4024                    stake_account_pubkey,
4025                    new_authorizations: vec![StakeAuthorizationIndexed {
4026                        authorization_type: StakeAuthorize::Staker,
4027                        new_authority_pubkey: stake_account_pubkey,
4028                        authority: 0,
4029                        new_authority_signer: None,
4030                    }],
4031                    sign_only: false,
4032                    dump_transaction_message: false,
4033                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4034                    nonce_account: None,
4035                    nonce_authority: 0,
4036                    memo: None,
4037                    fee_payer: 1,
4038                    custodian: None,
4039                    no_wait: false,
4040                    compute_unit_price: None,
4041                },
4042                signers: vec![
4043                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4044                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap()),
4045                ],
4046            }
4047        );
4048        // Test Authorize Subcommand w/ absentee fee-payer
4049        let sig = fee_payer_keypair.sign_message(&[0u8]);
4050        let signer = format!("{fee_payer_string}={sig}");
4051        let test_authorize = test_commands.clone().get_matches_from(vec![
4052            "test",
4053            "stake-authorize",
4054            &stake_account_string,
4055            "--new-stake-authority",
4056            &stake_account_string,
4057            "--fee-payer",
4058            &fee_payer_string,
4059            "--blockhash",
4060            &blockhash_string,
4061            "--signer",
4062            &signer,
4063        ]);
4064        assert_eq!(
4065            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4066            CliCommandInfo {
4067                command: CliCommand::StakeAuthorize {
4068                    stake_account_pubkey,
4069                    new_authorizations: vec![StakeAuthorizationIndexed {
4070                        authorization_type: StakeAuthorize::Staker,
4071                        new_authority_pubkey: stake_account_pubkey,
4072                        authority: 0,
4073                        new_authority_signer: None,
4074                    }],
4075                    sign_only: false,
4076                    dump_transaction_message: false,
4077                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4078                    nonce_account: None,
4079                    nonce_authority: 0,
4080                    memo: None,
4081                    fee_payer: 1,
4082                    custodian: None,
4083                    no_wait: false,
4084                    compute_unit_price: None,
4085                },
4086                signers: vec![
4087                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4088                    Box::new(Presigner::new(&fee_payer_pubkey, &sig))
4089                ],
4090            }
4091        );
4092
4093        // Test CreateStakeAccount SubCommand
4094        let custodian = solana_pubkey::new_rand();
4095        let custodian_string = format!("{custodian}");
4096        let authorized = solana_pubkey::new_rand();
4097        let authorized_string = format!("{authorized}");
4098        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4099            "test",
4100            "create-stake-account",
4101            &keypair_file,
4102            "50",
4103            "--stake-authority",
4104            &authorized_string,
4105            "--withdraw-authority",
4106            &authorized_string,
4107            "--custodian",
4108            &custodian_string,
4109            "--lockup-epoch",
4110            "43",
4111        ]);
4112        assert_eq!(
4113            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4114            CliCommandInfo {
4115                command: CliCommand::CreateStakeAccount {
4116                    stake_account: 1,
4117                    seed: None,
4118                    staker: Some(authorized),
4119                    withdrawer: Some(authorized),
4120                    withdrawer_signer: None,
4121                    lockup: Lockup {
4122                        epoch: 43,
4123                        unix_timestamp: 0,
4124                        custodian,
4125                    },
4126                    amount: SpendAmount::Some(50_000_000_000),
4127                    sign_only: false,
4128                    dump_transaction_message: false,
4129                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4130                    nonce_account: None,
4131                    nonce_authority: 0,
4132                    memo: None,
4133                    fee_payer: 0,
4134                    from: 0,
4135                    compute_unit_price: None,
4136                },
4137                signers: vec![
4138                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4139                    Box::new(stake_account_keypair)
4140                ],
4141            }
4142        );
4143
4144        let (keypair_file, mut tmp_file) = make_tmp_file();
4145        let stake_account_keypair = Keypair::new();
4146        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4147        let stake_account_pubkey = stake_account_keypair.pubkey();
4148        let stake_account_string = stake_account_pubkey.to_string();
4149
4150        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4151            "test",
4152            "create-stake-account",
4153            &keypair_file,
4154            "50",
4155        ]);
4156
4157        assert_eq!(
4158            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4159            CliCommandInfo {
4160                command: CliCommand::CreateStakeAccount {
4161                    stake_account: 1,
4162                    seed: None,
4163                    staker: None,
4164                    withdrawer: None,
4165                    withdrawer_signer: None,
4166                    lockup: Lockup::default(),
4167                    amount: SpendAmount::Some(50_000_000_000),
4168                    sign_only: false,
4169                    dump_transaction_message: false,
4170                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4171                    nonce_account: None,
4172                    nonce_authority: 0,
4173                    memo: None,
4174                    fee_payer: 0,
4175                    from: 0,
4176                    compute_unit_price: None,
4177                },
4178                signers: vec![
4179                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4180                    Box::new(read_keypair_file(&keypair_file).unwrap())
4181                ],
4182            }
4183        );
4184        let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
4185        let withdrawer_keypair = Keypair::new();
4186        write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
4187        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4188            "test",
4189            "create-stake-account-checked",
4190            &keypair_file,
4191            "50",
4192            "--stake-authority",
4193            &authorized_string,
4194            "--withdraw-authority",
4195            &withdrawer_keypair_file,
4196        ]);
4197        assert_eq!(
4198            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4199            CliCommandInfo {
4200                command: CliCommand::CreateStakeAccount {
4201                    stake_account: 1,
4202                    seed: None,
4203                    staker: Some(authorized),
4204                    withdrawer: Some(withdrawer_keypair.pubkey()),
4205                    withdrawer_signer: Some(2),
4206                    lockup: Lockup::default(),
4207                    amount: SpendAmount::Some(50_000_000_000),
4208                    sign_only: false,
4209                    dump_transaction_message: false,
4210                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4211                    nonce_account: None,
4212                    nonce_authority: 0,
4213                    memo: None,
4214                    fee_payer: 0,
4215                    from: 0,
4216                    compute_unit_price: None,
4217                },
4218                signers: vec![
4219                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4220                    Box::new(stake_account_keypair),
4221                    Box::new(withdrawer_keypair),
4222                ],
4223            }
4224        );
4225
4226        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4227            "test",
4228            "create-stake-account-checked",
4229            &keypair_file,
4230            "50",
4231            "--stake-authority",
4232            &authorized_string,
4233            "--withdraw-authority",
4234            &authorized_string,
4235        ]);
4236        assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());
4237
4238        // CreateStakeAccount offline and nonce
4239        let nonce_account = Pubkey::from([1u8; 32]);
4240        let nonce_account_string = nonce_account.to_string();
4241        let offline = keypair_from_seed(&[2u8; 32]).unwrap();
4242        let offline_pubkey = offline.pubkey();
4243        let offline_string = offline_pubkey.to_string();
4244        let offline_sig = offline.sign_message(&[3u8]);
4245        let offline_signer = format!("{offline_pubkey}={offline_sig}");
4246        let nonce_hash = Hash::new_from_array([4u8; 32]);
4247        let nonce_hash_string = nonce_hash.to_string();
4248        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4249            "test",
4250            "create-stake-account",
4251            &keypair_file,
4252            "50",
4253            "--blockhash",
4254            &nonce_hash_string,
4255            "--nonce",
4256            &nonce_account_string,
4257            "--nonce-authority",
4258            &offline_string,
4259            "--fee-payer",
4260            &offline_string,
4261            "--from",
4262            &offline_string,
4263            "--signer",
4264            &offline_signer,
4265        ]);
4266
4267        assert_eq!(
4268            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4269            CliCommandInfo {
4270                command: CliCommand::CreateStakeAccount {
4271                    stake_account: 1,
4272                    seed: None,
4273                    staker: None,
4274                    withdrawer: None,
4275                    withdrawer_signer: None,
4276                    lockup: Lockup::default(),
4277                    amount: SpendAmount::Some(50_000_000_000),
4278                    sign_only: false,
4279                    dump_transaction_message: false,
4280                    blockhash_query: BlockhashQuery::Validated(
4281                        Source::NonceAccount(nonce_account),
4282                        nonce_hash
4283                    ),
4284                    nonce_account: Some(nonce_account),
4285                    nonce_authority: 0,
4286                    memo: None,
4287                    fee_payer: 0,
4288                    from: 0,
4289                    compute_unit_price: None,
4290                },
4291                signers: vec![
4292                    Box::new(Presigner::new(&offline_pubkey, &offline_sig)),
4293                    Box::new(read_keypair_file(&keypair_file).unwrap())
4294                ],
4295            }
4296        );
4297
4298        // Test DelegateStake Subcommand
4299        let vote_account_pubkey = solana_pubkey::new_rand();
4300        let vote_account_string = vote_account_pubkey.to_string();
4301        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4302            "test",
4303            "delegate-stake",
4304            &stake_account_string,
4305            &vote_account_string,
4306        ]);
4307        assert_eq!(
4308            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4309            CliCommandInfo {
4310                command: CliCommand::DelegateStake {
4311                    stake_account_pubkey,
4312                    vote_account_pubkey,
4313                    stake_authority: 0,
4314                    force: false,
4315                    sign_only: false,
4316                    dump_transaction_message: false,
4317                    blockhash_query: BlockhashQuery::default(),
4318                    nonce_account: None,
4319                    nonce_authority: 0,
4320                    memo: None,
4321                    fee_payer: 0,
4322                    compute_unit_price: None,
4323                },
4324                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4325            }
4326        );
4327
4328        // Test DelegateStake Subcommand w/ authority
4329        let vote_account_pubkey = solana_pubkey::new_rand();
4330        let vote_account_string = vote_account_pubkey.to_string();
4331        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4332            "test",
4333            "delegate-stake",
4334            &stake_account_string,
4335            &vote_account_string,
4336            "--stake-authority",
4337            &stake_authority_keypair_file,
4338        ]);
4339        assert_eq!(
4340            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4341            CliCommandInfo {
4342                command: CliCommand::DelegateStake {
4343                    stake_account_pubkey,
4344                    vote_account_pubkey,
4345                    stake_authority: 1,
4346                    force: false,
4347                    sign_only: false,
4348                    dump_transaction_message: false,
4349                    blockhash_query: BlockhashQuery::default(),
4350                    nonce_account: None,
4351                    nonce_authority: 0,
4352                    memo: None,
4353                    fee_payer: 0,
4354                    compute_unit_price: None,
4355                },
4356                signers: vec![
4357                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4358                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4359                ],
4360            }
4361        );
4362
4363        // Test DelegateStake Subcommand w/ force
4364        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4365            "test",
4366            "delegate-stake",
4367            "--force",
4368            &stake_account_string,
4369            &vote_account_string,
4370        ]);
4371        assert_eq!(
4372            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4373            CliCommandInfo {
4374                command: CliCommand::DelegateStake {
4375                    stake_account_pubkey,
4376                    vote_account_pubkey,
4377                    stake_authority: 0,
4378                    force: true,
4379                    sign_only: false,
4380                    dump_transaction_message: false,
4381                    blockhash_query: BlockhashQuery::default(),
4382                    nonce_account: None,
4383                    nonce_authority: 0,
4384                    memo: None,
4385                    fee_payer: 0,
4386                    compute_unit_price: None,
4387                },
4388                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4389            }
4390        );
4391
4392        // Test Delegate Subcommand w/ Blockhash
4393        let blockhash = Hash::default();
4394        let blockhash_string = format!("{blockhash}");
4395        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4396            "test",
4397            "delegate-stake",
4398            &stake_account_string,
4399            &vote_account_string,
4400            "--blockhash",
4401            &blockhash_string,
4402        ]);
4403        assert_eq!(
4404            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4405            CliCommandInfo {
4406                command: CliCommand::DelegateStake {
4407                    stake_account_pubkey,
4408                    vote_account_pubkey,
4409                    stake_authority: 0,
4410                    force: false,
4411                    sign_only: false,
4412                    dump_transaction_message: false,
4413                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4414                    nonce_account: None,
4415                    nonce_authority: 0,
4416                    memo: None,
4417                    fee_payer: 0,
4418                    compute_unit_price: None,
4419                },
4420                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4421            }
4422        );
4423
4424        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4425            "test",
4426            "delegate-stake",
4427            &stake_account_string,
4428            &vote_account_string,
4429            "--blockhash",
4430            &blockhash_string,
4431            "--sign-only",
4432        ]);
4433        assert_eq!(
4434            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4435            CliCommandInfo {
4436                command: CliCommand::DelegateStake {
4437                    stake_account_pubkey,
4438                    vote_account_pubkey,
4439                    stake_authority: 0,
4440                    force: false,
4441                    sign_only: true,
4442                    dump_transaction_message: false,
4443                    blockhash_query: BlockhashQuery::Static(blockhash),
4444                    nonce_account: None,
4445                    nonce_authority: 0,
4446                    memo: None,
4447                    fee_payer: 0,
4448                    compute_unit_price: None,
4449                },
4450                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4451            }
4452        );
4453
4454        // Test Delegate Subcommand w/ absent fee payer
4455        let key1 = solana_pubkey::new_rand();
4456        let sig1 = Keypair::new().sign_message(&[0u8]);
4457        let signer1 = format!("{key1}={sig1}");
4458        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4459            "test",
4460            "delegate-stake",
4461            &stake_account_string,
4462            &vote_account_string,
4463            "--blockhash",
4464            &blockhash_string,
4465            "--signer",
4466            &signer1,
4467            "--fee-payer",
4468            &key1.to_string(),
4469        ]);
4470        assert_eq!(
4471            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4472            CliCommandInfo {
4473                command: CliCommand::DelegateStake {
4474                    stake_account_pubkey,
4475                    vote_account_pubkey,
4476                    stake_authority: 0,
4477                    force: false,
4478                    sign_only: false,
4479                    dump_transaction_message: false,
4480                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4481                    nonce_account: None,
4482                    nonce_authority: 0,
4483                    memo: None,
4484                    fee_payer: 1,
4485                    compute_unit_price: None,
4486                },
4487                signers: vec![
4488                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4489                    Box::new(Presigner::new(&key1, &sig1))
4490                ],
4491            }
4492        );
4493
4494        // Test Delegate Subcommand w/ absent fee payer and absent nonce authority
4495        let key2 = solana_pubkey::new_rand();
4496        let sig2 = Keypair::new().sign_message(&[0u8]);
4497        let signer2 = format!("{key2}={sig2}");
4498        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4499            "test",
4500            "delegate-stake",
4501            &stake_account_string,
4502            &vote_account_string,
4503            "--blockhash",
4504            &blockhash_string,
4505            "--signer",
4506            &signer1,
4507            "--signer",
4508            &signer2,
4509            "--fee-payer",
4510            &key1.to_string(),
4511            "--nonce",
4512            &nonce_account.to_string(),
4513            "--nonce-authority",
4514            &key2.to_string(),
4515        ]);
4516        assert_eq!(
4517            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4518            CliCommandInfo {
4519                command: CliCommand::DelegateStake {
4520                    stake_account_pubkey,
4521                    vote_account_pubkey,
4522                    stake_authority: 0,
4523                    force: false,
4524                    sign_only: false,
4525                    dump_transaction_message: false,
4526                    blockhash_query: BlockhashQuery::Validated(
4527                        Source::NonceAccount(nonce_account),
4528                        blockhash
4529                    ),
4530                    nonce_account: Some(nonce_account),
4531                    nonce_authority: 2,
4532                    memo: None,
4533                    fee_payer: 1,
4534                    compute_unit_price: None,
4535                },
4536                signers: vec![
4537                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4538                    Box::new(Presigner::new(&key1, &sig1)),
4539                    Box::new(Presigner::new(&key2, &sig2)),
4540                ],
4541            }
4542        );
4543
4544        // Test Delegate Subcommand w/ present fee-payer
4545        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4546        let fee_payer_keypair = Keypair::new();
4547        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4548        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4549            "test",
4550            "delegate-stake",
4551            &stake_account_string,
4552            &vote_account_string,
4553            "--fee-payer",
4554            &fee_payer_keypair_file,
4555        ]);
4556        assert_eq!(
4557            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4558            CliCommandInfo {
4559                command: CliCommand::DelegateStake {
4560                    stake_account_pubkey,
4561                    vote_account_pubkey,
4562                    stake_authority: 0,
4563                    force: false,
4564                    sign_only: false,
4565                    dump_transaction_message: false,
4566                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4567                    nonce_account: None,
4568                    nonce_authority: 0,
4569                    memo: None,
4570                    fee_payer: 1,
4571                    compute_unit_price: None,
4572                },
4573                signers: vec![
4574                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4575                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
4576                ],
4577            }
4578        );
4579
4580        // Test WithdrawStake Subcommand
4581        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4582            "test",
4583            "withdraw-stake",
4584            &stake_account_string,
4585            &stake_account_string,
4586            "42",
4587        ]);
4588
4589        assert_eq!(
4590            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4591            CliCommandInfo {
4592                command: CliCommand::WithdrawStake {
4593                    stake_account_pubkey,
4594                    destination_account_pubkey: stake_account_pubkey,
4595                    amount: SpendAmount::Some(42_000_000_000),
4596                    withdraw_authority: 0,
4597                    custodian: None,
4598                    sign_only: false,
4599                    dump_transaction_message: false,
4600                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4601                    nonce_account: None,
4602                    nonce_authority: 0,
4603                    memo: None,
4604                    seed: None,
4605                    fee_payer: 0,
4606                    compute_unit_price: None,
4607                },
4608                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4609            }
4610        );
4611
4612        // Test WithdrawStake Subcommand w/ AVAILABLE amount
4613        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4614            "test",
4615            "withdraw-stake",
4616            &stake_account_string,
4617            &stake_account_string,
4618            "AVAILABLE",
4619        ]);
4620
4621        assert_eq!(
4622            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4623            CliCommandInfo {
4624                command: CliCommand::WithdrawStake {
4625                    stake_account_pubkey,
4626                    destination_account_pubkey: stake_account_pubkey,
4627                    amount: SpendAmount::Available,
4628                    withdraw_authority: 0,
4629                    custodian: None,
4630                    sign_only: false,
4631                    dump_transaction_message: false,
4632                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4633                    nonce_account: None,
4634                    nonce_authority: 0,
4635                    memo: None,
4636                    seed: None,
4637                    fee_payer: 0,
4638                    compute_unit_price: None,
4639                },
4640                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4641            }
4642        );
4643
4644        // Test WithdrawStake Subcommand w/ ComputeUnitPrice
4645        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4646            "test",
4647            "withdraw-stake",
4648            &stake_account_string,
4649            &stake_account_string,
4650            "42",
4651            "--with-compute-unit-price",
4652            "99",
4653        ]);
4654
4655        assert_eq!(
4656            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4657            CliCommandInfo {
4658                command: CliCommand::WithdrawStake {
4659                    stake_account_pubkey,
4660                    destination_account_pubkey: stake_account_pubkey,
4661                    amount: SpendAmount::Some(42_000_000_000),
4662                    withdraw_authority: 0,
4663                    custodian: None,
4664                    sign_only: false,
4665                    dump_transaction_message: false,
4666                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4667                    nonce_account: None,
4668                    nonce_authority: 0,
4669                    memo: None,
4670                    seed: None,
4671                    fee_payer: 0,
4672                    compute_unit_price: Some(99),
4673                },
4674                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4675            }
4676        );
4677
4678        // Test WithdrawStake Subcommand w/ authority
4679        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4680            "test",
4681            "withdraw-stake",
4682            &stake_account_string,
4683            &stake_account_string,
4684            "42",
4685            "--withdraw-authority",
4686            &stake_authority_keypair_file,
4687        ]);
4688
4689        assert_eq!(
4690            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4691            CliCommandInfo {
4692                command: CliCommand::WithdrawStake {
4693                    stake_account_pubkey,
4694                    destination_account_pubkey: stake_account_pubkey,
4695                    amount: SpendAmount::Some(42_000_000_000),
4696                    withdraw_authority: 1,
4697                    custodian: None,
4698                    sign_only: false,
4699                    dump_transaction_message: false,
4700                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4701                    nonce_account: None,
4702                    nonce_authority: 0,
4703                    memo: None,
4704                    seed: None,
4705                    fee_payer: 0,
4706                    compute_unit_price: None,
4707                },
4708                signers: vec![
4709                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4710                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4711                ],
4712            }
4713        );
4714
4715        // Test WithdrawStake Subcommand w/ custodian
4716        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4717            "test",
4718            "withdraw-stake",
4719            &stake_account_string,
4720            &stake_account_string,
4721            "42",
4722            "--custodian",
4723            &custodian_keypair_file,
4724        ]);
4725
4726        assert_eq!(
4727            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4728            CliCommandInfo {
4729                command: CliCommand::WithdrawStake {
4730                    stake_account_pubkey,
4731                    destination_account_pubkey: stake_account_pubkey,
4732                    amount: SpendAmount::Some(42_000_000_000),
4733                    withdraw_authority: 0,
4734                    custodian: Some(1),
4735                    sign_only: false,
4736                    dump_transaction_message: false,
4737                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4738                    nonce_account: None,
4739                    nonce_authority: 0,
4740                    memo: None,
4741                    seed: None,
4742                    fee_payer: 0,
4743                    compute_unit_price: None,
4744                },
4745                signers: vec![
4746                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4747                    Box::new(read_keypair_file(&custodian_keypair_file).unwrap())
4748                ],
4749            }
4750        );
4751
4752        // Test WithdrawStake Subcommand w/ authority and offline nonce
4753        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4754            "test",
4755            "withdraw-stake",
4756            &stake_account_string,
4757            &stake_account_string,
4758            "42",
4759            "--withdraw-authority",
4760            &stake_authority_keypair_file,
4761            "--blockhash",
4762            &nonce_hash_string,
4763            "--nonce",
4764            &nonce_account_string,
4765            "--nonce-authority",
4766            &offline_string,
4767            "--fee-payer",
4768            &offline_string,
4769            "--signer",
4770            &offline_signer,
4771        ]);
4772
4773        assert_eq!(
4774            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4775            CliCommandInfo {
4776                command: CliCommand::WithdrawStake {
4777                    stake_account_pubkey,
4778                    destination_account_pubkey: stake_account_pubkey,
4779                    amount: SpendAmount::Some(42_000_000_000),
4780                    withdraw_authority: 0,
4781                    custodian: None,
4782                    sign_only: false,
4783                    dump_transaction_message: false,
4784                    blockhash_query: BlockhashQuery::Validated(
4785                        Source::NonceAccount(nonce_account),
4786                        nonce_hash
4787                    ),
4788                    nonce_account: Some(nonce_account),
4789                    nonce_authority: 1,
4790                    memo: None,
4791                    seed: None,
4792                    fee_payer: 1,
4793                    compute_unit_price: None,
4794                },
4795                signers: vec![
4796                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
4797                    Box::new(Presigner::new(&offline_pubkey, &offline_sig))
4798                ],
4799            }
4800        );
4801
4802        // Test DeactivateStake Subcommand
4803        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4804            "test",
4805            "deactivate-stake",
4806            &stake_account_string,
4807        ]);
4808        assert_eq!(
4809            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4810            CliCommandInfo {
4811                command: CliCommand::DeactivateStake {
4812                    stake_account_pubkey,
4813                    stake_authority: 0,
4814                    sign_only: false,
4815                    deactivate_delinquent: false,
4816                    dump_transaction_message: false,
4817                    blockhash_query: BlockhashQuery::default(),
4818                    nonce_account: None,
4819                    nonce_authority: 0,
4820                    memo: None,
4821                    seed: None,
4822                    fee_payer: 0,
4823                    compute_unit_price: None,
4824                },
4825                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4826            }
4827        );
4828
4829        // Test DeactivateStake Subcommand with delinquent flag
4830        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4831            "test",
4832            "deactivate-stake",
4833            &stake_account_string,
4834            "--delinquent",
4835        ]);
4836        assert_eq!(
4837            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4838            CliCommandInfo {
4839                command: CliCommand::DeactivateStake {
4840                    stake_account_pubkey,
4841                    stake_authority: 0,
4842                    sign_only: false,
4843                    deactivate_delinquent: true,
4844                    dump_transaction_message: false,
4845                    blockhash_query: BlockhashQuery::default(),
4846                    nonce_account: None,
4847                    nonce_authority: 0,
4848                    memo: None,
4849                    seed: None,
4850                    fee_payer: 0,
4851                    compute_unit_price: None,
4852                },
4853                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4854            }
4855        );
4856
4857        // Test DeactivateStake Subcommand w/ authority
4858        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4859            "test",
4860            "deactivate-stake",
4861            &stake_account_string,
4862            "--stake-authority",
4863            &stake_authority_keypair_file,
4864        ]);
4865        assert_eq!(
4866            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4867            CliCommandInfo {
4868                command: CliCommand::DeactivateStake {
4869                    stake_account_pubkey,
4870                    stake_authority: 1,
4871                    sign_only: false,
4872                    deactivate_delinquent: false,
4873                    dump_transaction_message: false,
4874                    blockhash_query: BlockhashQuery::default(),
4875                    nonce_account: None,
4876                    nonce_authority: 0,
4877                    memo: None,
4878                    seed: None,
4879                    fee_payer: 0,
4880                    compute_unit_price: None,
4881                },
4882                signers: vec![
4883                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4884                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4885                ],
4886            }
4887        );
4888
4889        // Test Deactivate Subcommand w/ Blockhash
4890        let blockhash = Hash::default();
4891        let blockhash_string = format!("{blockhash}");
4892        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4893            "test",
4894            "deactivate-stake",
4895            &stake_account_string,
4896            "--blockhash",
4897            &blockhash_string,
4898        ]);
4899        assert_eq!(
4900            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4901            CliCommandInfo {
4902                command: CliCommand::DeactivateStake {
4903                    stake_account_pubkey,
4904                    stake_authority: 0,
4905                    sign_only: false,
4906                    deactivate_delinquent: false,
4907                    dump_transaction_message: false,
4908                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4909                    nonce_account: None,
4910                    nonce_authority: 0,
4911                    memo: None,
4912                    seed: None,
4913                    fee_payer: 0,
4914                    compute_unit_price: None,
4915                },
4916                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4917            }
4918        );
4919
4920        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4921            "test",
4922            "deactivate-stake",
4923            &stake_account_string,
4924            "--blockhash",
4925            &blockhash_string,
4926            "--sign-only",
4927        ]);
4928        assert_eq!(
4929            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4930            CliCommandInfo {
4931                command: CliCommand::DeactivateStake {
4932                    stake_account_pubkey,
4933                    stake_authority: 0,
4934                    sign_only: true,
4935                    deactivate_delinquent: false,
4936                    dump_transaction_message: false,
4937                    blockhash_query: BlockhashQuery::Static(blockhash),
4938                    nonce_account: None,
4939                    nonce_authority: 0,
4940                    memo: None,
4941                    seed: None,
4942                    fee_payer: 0,
4943                    compute_unit_price: None,
4944                },
4945                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4946            }
4947        );
4948
4949        // Test Deactivate Subcommand w/ absent fee payer
4950        let key1 = solana_pubkey::new_rand();
4951        let sig1 = Keypair::new().sign_message(&[0u8]);
4952        let signer1 = format!("{key1}={sig1}");
4953        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4954            "test",
4955            "deactivate-stake",
4956            &stake_account_string,
4957            "--blockhash",
4958            &blockhash_string,
4959            "--signer",
4960            &signer1,
4961            "--fee-payer",
4962            &key1.to_string(),
4963        ]);
4964        assert_eq!(
4965            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4966            CliCommandInfo {
4967                command: CliCommand::DeactivateStake {
4968                    stake_account_pubkey,
4969                    stake_authority: 0,
4970                    sign_only: false,
4971                    deactivate_delinquent: false,
4972                    dump_transaction_message: false,
4973                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4974                    nonce_account: None,
4975                    nonce_authority: 0,
4976                    memo: None,
4977                    seed: None,
4978                    fee_payer: 1,
4979                    compute_unit_price: None,
4980                },
4981                signers: vec![
4982                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4983                    Box::new(Presigner::new(&key1, &sig1))
4984                ],
4985            }
4986        );
4987
4988        // Test Deactivate Subcommand w/ absent fee payer and nonce authority
4989        let key2 = solana_pubkey::new_rand();
4990        let sig2 = Keypair::new().sign_message(&[0u8]);
4991        let signer2 = format!("{key2}={sig2}");
4992        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4993            "test",
4994            "deactivate-stake",
4995            &stake_account_string,
4996            "--blockhash",
4997            &blockhash_string,
4998            "--signer",
4999            &signer1,
5000            "--signer",
5001            &signer2,
5002            "--fee-payer",
5003            &key1.to_string(),
5004            "--nonce",
5005            &nonce_account.to_string(),
5006            "--nonce-authority",
5007            &key2.to_string(),
5008        ]);
5009        assert_eq!(
5010            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5011            CliCommandInfo {
5012                command: CliCommand::DeactivateStake {
5013                    stake_account_pubkey,
5014                    stake_authority: 0,
5015                    sign_only: false,
5016                    deactivate_delinquent: false,
5017                    dump_transaction_message: false,
5018                    blockhash_query: BlockhashQuery::Validated(
5019                        Source::NonceAccount(nonce_account),
5020                        blockhash
5021                    ),
5022                    nonce_account: Some(nonce_account),
5023                    nonce_authority: 2,
5024                    memo: None,
5025                    seed: None,
5026                    fee_payer: 1,
5027                    compute_unit_price: None,
5028                },
5029                signers: vec![
5030                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5031                    Box::new(Presigner::new(&key1, &sig1)),
5032                    Box::new(Presigner::new(&key2, &sig2)),
5033                ],
5034            }
5035        );
5036
5037        // Test Deactivate Subcommand w/ fee-payer
5038        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5039            "test",
5040            "deactivate-stake",
5041            &stake_account_string,
5042            "--fee-payer",
5043            &fee_payer_keypair_file,
5044        ]);
5045        assert_eq!(
5046            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5047            CliCommandInfo {
5048                command: CliCommand::DeactivateStake {
5049                    stake_account_pubkey,
5050                    stake_authority: 0,
5051                    sign_only: false,
5052                    deactivate_delinquent: false,
5053                    dump_transaction_message: false,
5054                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
5055                    nonce_account: None,
5056                    nonce_authority: 0,
5057                    memo: None,
5058                    seed: None,
5059                    fee_payer: 1,
5060                    compute_unit_price: None,
5061                },
5062                signers: vec![
5063                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5064                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
5065                ],
5066            }
5067        );
5068
5069        // Test SplitStake SubCommand
5070        let (keypair_file, mut tmp_file) = make_tmp_file();
5071        let stake_account_keypair = Keypair::new();
5072        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5073        let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
5074        let split_stake_account_keypair = Keypair::new();
5075        write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5076
5077        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5078            "test",
5079            "split-stake",
5080            &keypair_file,
5081            &split_stake_account_keypair_file,
5082            "50",
5083        ]);
5084        assert_eq!(
5085            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5086            CliCommandInfo {
5087                command: CliCommand::SplitStake {
5088                    stake_account_pubkey: stake_account_keypair.pubkey(),
5089                    stake_authority: 0,
5090                    sign_only: false,
5091                    dump_transaction_message: false,
5092                    blockhash_query: BlockhashQuery::default(),
5093                    nonce_account: None,
5094                    nonce_authority: 0,
5095                    memo: None,
5096                    split_stake_account: 1,
5097                    seed: None,
5098                    lamports: 50_000_000_000,
5099                    fee_payer: 0,
5100                    compute_unit_price: None,
5101                    rent_exempt_reserve: None,
5102                },
5103                signers: vec![
5104                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5105                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap())
5106                ],
5107            }
5108        );
5109
5110        // Split stake offline nonced submission
5111        let nonce_account = Pubkey::from([1u8; 32]);
5112        let nonce_account_string = nonce_account.to_string();
5113        let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
5114        let nonce_auth_pubkey = nonce_auth.pubkey();
5115        let nonce_auth_string = nonce_auth_pubkey.to_string();
5116        let nonce_sig = nonce_auth.sign_message(&[0u8]);
5117        let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
5118        let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
5119        let stake_auth_pubkey = stake_auth.pubkey();
5120        let stake_auth_string = stake_auth_pubkey.to_string();
5121        let stake_sig = stake_auth.sign_message(&[0u8]);
5122        let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
5123        let nonce_hash = Hash::new_from_array([4u8; 32]);
5124        let nonce_hash_string = nonce_hash.to_string();
5125
5126        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5127            "test",
5128            "split-stake",
5129            &keypair_file,
5130            &split_stake_account_keypair_file,
5131            "50",
5132            "--stake-authority",
5133            &stake_auth_string,
5134            "--blockhash",
5135            &nonce_hash_string,
5136            "--nonce",
5137            &nonce_account_string,
5138            "--nonce-authority",
5139            &nonce_auth_string,
5140            "--fee-payer",
5141            &nonce_auth_string, // Arbitrary choice of fee-payer
5142            "--signer",
5143            &nonce_signer,
5144            "--signer",
5145            &stake_signer,
5146        ]);
5147        assert_eq!(
5148            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5149            CliCommandInfo {
5150                command: CliCommand::SplitStake {
5151                    stake_account_pubkey: stake_account_keypair.pubkey(),
5152                    stake_authority: 0,
5153                    sign_only: false,
5154                    dump_transaction_message: false,
5155                    blockhash_query: BlockhashQuery::Validated(
5156                        Source::NonceAccount(nonce_account),
5157                        nonce_hash
5158                    ),
5159                    nonce_account: Some(nonce_account),
5160                    nonce_authority: 1,
5161                    memo: None,
5162                    split_stake_account: 2,
5163                    seed: None,
5164                    lamports: 50_000_000_000,
5165                    fee_payer: 1,
5166                    compute_unit_price: None,
5167                    rent_exempt_reserve: None,
5168                },
5169                signers: vec![
5170                    Box::new(Presigner::new(&stake_auth_pubkey, &stake_sig)),
5171                    Box::new(Presigner::new(&nonce_auth_pubkey, &nonce_sig)),
5172                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap()),
5173                ],
5174            }
5175        );
5176
5177        // Test MergeStake SubCommand
5178        let (keypair_file, mut tmp_file) = make_tmp_file();
5179        let stake_account_keypair = Keypair::new();
5180        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5181
5182        let source_stake_account_pubkey = solana_pubkey::new_rand();
5183        let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
5184            "test",
5185            "merge-stake",
5186            &keypair_file,
5187            &source_stake_account_pubkey.to_string(),
5188        ]);
5189        assert_eq!(
5190            parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
5191            CliCommandInfo {
5192                command: CliCommand::MergeStake {
5193                    stake_account_pubkey: stake_account_keypair.pubkey(),
5194                    source_stake_account_pubkey,
5195                    stake_authority: 0,
5196                    sign_only: false,
5197                    dump_transaction_message: false,
5198                    blockhash_query: BlockhashQuery::default(),
5199                    nonce_account: None,
5200                    nonce_authority: 0,
5201                    memo: None,
5202                    fee_payer: 0,
5203                    compute_unit_price: None,
5204                },
5205                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
5206            }
5207        );
5208    }
5209}