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