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,
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        check_account_for_fee_with_commitment(
1690            rpc_client,
1691            &tx.message.account_keys[0],
1692            &tx.message,
1693            config.commitment,
1694        )
1695        .await?;
1696        let result = if no_wait {
1697            rpc_client
1698                .send_transaction_with_config(&tx, config.send_transaction_config)
1699                .await
1700        } else {
1701            rpc_client
1702                .send_and_confirm_transaction_with_spinner_and_config(
1703                    &tx,
1704                    config.commitment,
1705                    config.send_transaction_config,
1706                )
1707                .await
1708        };
1709        log_instruction_custom_error::<StakeError>(result, config)
1710    }
1711}
1712
1713#[allow(clippy::too_many_arguments)]
1714pub async fn process_deactivate_stake_account(
1715    rpc_client: &RpcClient,
1716    config: &CliConfig<'_>,
1717    stake_account_pubkey: &Pubkey,
1718    stake_authority: SignerIndex,
1719    sign_only: bool,
1720    deactivate_delinquent: bool,
1721    dump_transaction_message: bool,
1722    blockhash_query: &BlockhashQuery,
1723    nonce_account: Option<Pubkey>,
1724    nonce_authority: SignerIndex,
1725    memo: Option<&String>,
1726    seed: Option<&String>,
1727    fee_payer: SignerIndex,
1728    compute_unit_price: Option<u64>,
1729) -> ProcessResult {
1730    let recent_blockhash = blockhash_query
1731        .get_blockhash(rpc_client, config.commitment)
1732        .await?;
1733
1734    let stake_account_address = if let Some(seed) = seed {
1735        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1736    } else {
1737        *stake_account_pubkey
1738    };
1739
1740    // DeactivateDelinquent parses a VoteState, which may change between simulation and execution
1741    let compute_unit_limit = match blockhash_query {
1742        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1743        BlockhashQuery::Rpc(_) if deactivate_delinquent => {
1744            ComputeUnitLimit::SimulatedWithExtraPercentage(5)
1745        }
1746        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1747    };
1748    let ixs = vec![if deactivate_delinquent {
1749        let stake_account = rpc_client.get_account(&stake_account_address).await?;
1750        if stake_account.owner != stake::program::id() {
1751            return Err(CliError::BadParameter(format!(
1752                "{stake_account_address} is not a stake account",
1753            ))
1754            .into());
1755        }
1756
1757        let vote_account_address = match wincode::deserialize::<StakeStateV2>(&stake_account.data) {
1758            Ok(stake_state) => match stake_state {
1759                StakeStateV2::Stake(_, stake, _) => stake.delegation.voter_pubkey,
1760                _ => {
1761                    return Err(CliError::BadParameter(format!(
1762                        "{stake_account_address} is not a delegated stake account",
1763                    ))
1764                    .into());
1765                }
1766            },
1767            Err(err) => {
1768                return Err(CliError::RpcRequestError(format!(
1769                    "Account data could not be deserialized to stake state: {err}"
1770                ))
1771                .into());
1772            }
1773        };
1774
1775        let current_epoch = rpc_client.get_epoch_info().await?.epoch;
1776
1777        let (_, vote_state) = crate::vote::get_vote_account(
1778            rpc_client,
1779            &vote_account_address,
1780            rpc_client.commitment(),
1781        )
1782        .await?;
1783        if !eligible_for_deactivate_delinquent(&vote_state.epoch_credits, current_epoch) {
1784            return Err(CliError::BadParameter(format!(
1785                "Stake has not been delinquent for {} epochs",
1786                stake::MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION,
1787            ))
1788            .into());
1789        }
1790
1791        // Search for a reference vote account
1792        let reference_vote_account_address = rpc_client
1793            .get_vote_accounts()
1794            .await?
1795            .current
1796            .into_iter()
1797            .find(|vote_account_info| {
1798                acceptable_reference_epoch_credits(&vote_account_info.epoch_credits, current_epoch)
1799            });
1800        let reference_vote_account_address = reference_vote_account_address
1801            .ok_or_else(|| {
1802                CliError::RpcRequestError("Unable to find a reference vote account".into())
1803            })?
1804            .vote_pubkey
1805            .parse()?;
1806
1807        stake_instruction::deactivate_delinquent_stake(
1808            &stake_account_address,
1809            &vote_account_address,
1810            &reference_vote_account_address,
1811        )
1812    } else {
1813        let stake_authority = config.signers[stake_authority];
1814        stake_instruction::deactivate_stake(&stake_account_address, &stake_authority.pubkey())
1815    }]
1816    .with_memo(memo)
1817    .with_compute_unit_config(&ComputeUnitConfig {
1818        compute_unit_price,
1819        compute_unit_limit,
1820    });
1821
1822    let nonce_authority = config.signers[nonce_authority];
1823    let fee_payer = config.signers[fee_payer];
1824
1825    let mut message = if let Some(nonce_account) = &nonce_account {
1826        Message::new_with_nonce(
1827            ixs,
1828            Some(&fee_payer.pubkey()),
1829            nonce_account,
1830            &nonce_authority.pubkey(),
1831        )
1832    } else {
1833        Message::new(&ixs, Some(&fee_payer.pubkey()))
1834    };
1835    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1836    let mut tx = Transaction::new_unsigned(message);
1837
1838    if sign_only {
1839        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1840        return_signers_with_config(
1841            &tx,
1842            &config.output_format,
1843            &ReturnSignersConfig {
1844                dump_transaction_message,
1845            },
1846        )
1847    } else {
1848        tx.try_sign(&config.signers, recent_blockhash)?;
1849        if let Some(nonce_account) = &nonce_account {
1850            let nonce_account =
1851                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1852                    rpc_client,
1853                    nonce_account,
1854                    config.commitment,
1855                )
1856                .await?;
1857            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1858        }
1859        check_account_for_fee_with_commitment(
1860            rpc_client,
1861            &tx.message.account_keys[0],
1862            &tx.message,
1863            config.commitment,
1864        )
1865        .await?;
1866        let result = rpc_client
1867            .send_and_confirm_transaction_with_spinner_and_config(
1868                &tx,
1869                config.commitment,
1870                config.send_transaction_config,
1871            )
1872            .await;
1873        log_instruction_custom_error::<StakeError>(result, config)
1874    }
1875}
1876
1877#[allow(clippy::too_many_arguments)]
1878pub async fn process_withdraw_stake(
1879    rpc_client: &RpcClient,
1880    config: &CliConfig<'_>,
1881    stake_account_pubkey: &Pubkey,
1882    destination_account_pubkey: &Pubkey,
1883    amount: SpendAmount,
1884    withdraw_authority: SignerIndex,
1885    custodian: Option<SignerIndex>,
1886    sign_only: bool,
1887    dump_transaction_message: bool,
1888    blockhash_query: &BlockhashQuery,
1889    nonce_account: Option<&Pubkey>,
1890    nonce_authority: SignerIndex,
1891    memo: Option<&String>,
1892    seed: Option<&String>,
1893    fee_payer: SignerIndex,
1894    compute_unit_price: Option<u64>,
1895) -> ProcessResult {
1896    let withdraw_authority = config.signers[withdraw_authority];
1897    let custodian = custodian.map(|index| config.signers[index]);
1898
1899    let stake_account_address = if let Some(seed) = seed {
1900        Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1901    } else {
1902        *stake_account_pubkey
1903    };
1904
1905    let recent_blockhash = blockhash_query
1906        .get_blockhash(rpc_client, config.commitment)
1907        .await?;
1908
1909    let fee_payer = config.signers[fee_payer];
1910    let nonce_authority = config.signers[nonce_authority];
1911
1912    let compute_unit_limit = match blockhash_query {
1913        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1914        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1915    };
1916    let build_message = |lamports| {
1917        let ixs = vec![stake_instruction::withdraw(
1918            &stake_account_address,
1919            &withdraw_authority.pubkey(),
1920            destination_account_pubkey,
1921            lamports,
1922            custodian.map(|signer| signer.pubkey()).as_ref(),
1923        )]
1924        .with_memo(memo)
1925        .with_compute_unit_config(&ComputeUnitConfig {
1926            compute_unit_price,
1927            compute_unit_limit,
1928        });
1929
1930        if let Some(nonce_account) = &nonce_account {
1931            Message::new_with_nonce(
1932                ixs,
1933                Some(&fee_payer.pubkey()),
1934                nonce_account,
1935                &nonce_authority.pubkey(),
1936            )
1937        } else {
1938            Message::new(&ixs, Some(&fee_payer.pubkey()))
1939        }
1940    };
1941
1942    let (message, _) = resolve_spend_tx_and_check_account_balances(
1943        rpc_client,
1944        sign_only,
1945        amount,
1946        &recent_blockhash,
1947        &stake_account_address,
1948        &fee_payer.pubkey(),
1949        compute_unit_limit,
1950        build_message,
1951        config.commitment,
1952    )
1953    .await?;
1954
1955    let mut tx = Transaction::new_unsigned(message);
1956
1957    if sign_only {
1958        tx.try_partial_sign(&config.signers, recent_blockhash)?;
1959        return_signers_with_config(
1960            &tx,
1961            &config.output_format,
1962            &ReturnSignersConfig {
1963                dump_transaction_message,
1964            },
1965        )
1966    } else {
1967        tx.try_sign(&config.signers, recent_blockhash)?;
1968        if let Some(nonce_account) = &nonce_account {
1969            let nonce_account =
1970                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1971                    rpc_client,
1972                    nonce_account,
1973                    config.commitment,
1974                )
1975                .await?;
1976            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1977        }
1978        check_account_for_fee_with_commitment(
1979            rpc_client,
1980            &tx.message.account_keys[0],
1981            &tx.message,
1982            config.commitment,
1983        )
1984        .await?;
1985        let result = rpc_client
1986            .send_and_confirm_transaction_with_spinner_and_config(
1987                &tx,
1988                config.commitment,
1989                config.send_transaction_config,
1990            )
1991            .await;
1992        log_instruction_custom_error::<StakeError>(result, config)
1993    }
1994}
1995
1996#[allow(clippy::too_many_arguments)]
1997pub async fn process_split_stake(
1998    rpc_client: &RpcClient,
1999    config: &CliConfig<'_>,
2000    stake_account_pubkey: &Pubkey,
2001    stake_authority: SignerIndex,
2002    sign_only: bool,
2003    dump_transaction_message: bool,
2004    blockhash_query: &BlockhashQuery,
2005    nonce_account: Option<Pubkey>,
2006    nonce_authority: SignerIndex,
2007    memo: Option<&String>,
2008    split_stake_account: SignerIndex,
2009    split_stake_account_seed: &Option<String>,
2010    lamports: u64,
2011    fee_payer: SignerIndex,
2012    compute_unit_price: Option<u64>,
2013    rent_exempt_reserve: Option<&u64>,
2014) -> ProcessResult {
2015    let split_stake_account = config.signers[split_stake_account];
2016    let fee_payer = config.signers[fee_payer];
2017
2018    if split_stake_account_seed.is_none() {
2019        check_unique_pubkeys(
2020            (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2021            (
2022                &split_stake_account.pubkey(),
2023                "split_stake_account".to_string(),
2024            ),
2025        )?;
2026    }
2027    check_unique_pubkeys(
2028        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2029        (stake_account_pubkey, "stake_account".to_string()),
2030    )?;
2031    check_unique_pubkeys(
2032        (stake_account_pubkey, "stake_account".to_string()),
2033        (
2034            &split_stake_account.pubkey(),
2035            "split_stake_account".to_string(),
2036        ),
2037    )?;
2038
2039    let stake_authority = config.signers[stake_authority];
2040
2041    let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
2042        Pubkey::create_with_seed(&split_stake_account.pubkey(), seed, &stake::program::id())?
2043    } else {
2044        split_stake_account.pubkey()
2045    };
2046
2047    let rent_exempt_reserve = if let Some(rent_exempt_reserve) = rent_exempt_reserve {
2048        *rent_exempt_reserve
2049    } else {
2050        let stake_minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2051        if lamports < stake_minimum_delegation {
2052            let lamports = Sol(lamports);
2053            let stake_minimum_delegation = Sol(stake_minimum_delegation);
2054            return Err(CliError::BadParameter(format!(
2055                "need at least {stake_minimum_delegation} for minimum stake delegation, provided: \
2056                 {lamports}"
2057            ))
2058            .into());
2059        }
2060
2061        let check_stake_account = |account: Account| -> Result<u64, CliError> {
2062            match account.owner {
2063                owner if owner == stake::program::id() => Err(CliError::BadParameter(format!(
2064                    "Stake account {split_stake_account_address} already exists"
2065                ))),
2066                owner if owner == system_program::id() => {
2067                    if !account.data.is_empty() {
2068                        Err(CliError::BadParameter(format!(
2069                            "Account {split_stake_account_address} has data and cannot be used to \
2070                             split stake"
2071                        )))
2072                    } else {
2073                        // if `stake_account`'s owner is the system_program and its data is
2074                        // empty, `stake_account` is allowed to receive the stake split
2075                        Ok(account.lamports)
2076                    }
2077                }
2078                _ => Err(CliError::BadParameter(format!(
2079                    "Account {split_stake_account_address} already exists and cannot be used to \
2080                     split stake"
2081                ))),
2082            }
2083        };
2084        let current_balance =
2085            if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address).await {
2086                check_stake_account(stake_account)?
2087            } else {
2088                0
2089            };
2090
2091        let rent_exempt_reserve = rpc_client
2092            .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())
2093            .await?;
2094
2095        rent_exempt_reserve.saturating_sub(current_balance)
2096    };
2097
2098    let recent_blockhash = blockhash_query
2099        .get_blockhash(rpc_client, config.commitment)
2100        .await?;
2101
2102    let mut ixs = vec![];
2103    if rent_exempt_reserve > 0 {
2104        ixs.push(system_instruction::transfer(
2105            &fee_payer.pubkey(),
2106            &split_stake_account_address,
2107            rent_exempt_reserve,
2108        ));
2109    }
2110    let compute_unit_limit = match blockhash_query {
2111        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2112        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2113    };
2114    if let Some(seed) = split_stake_account_seed {
2115        ixs.append(
2116            &mut stake_instruction::split_with_seed(
2117                stake_account_pubkey,
2118                &stake_authority.pubkey(),
2119                lamports,
2120                &split_stake_account_address,
2121                &split_stake_account.pubkey(),
2122                seed,
2123            )
2124            .with_memo(memo)
2125            .with_compute_unit_config(&ComputeUnitConfig {
2126                compute_unit_price,
2127                compute_unit_limit,
2128            }),
2129        )
2130    } else {
2131        ixs.append(
2132            &mut stake_instruction::split(
2133                stake_account_pubkey,
2134                &stake_authority.pubkey(),
2135                lamports,
2136                &split_stake_account_address,
2137            )
2138            .with_memo(memo)
2139            .with_compute_unit_config(&ComputeUnitConfig {
2140                compute_unit_price,
2141                compute_unit_limit,
2142            }),
2143        )
2144    };
2145
2146    let nonce_authority = config.signers[nonce_authority];
2147
2148    let mut message = if let Some(nonce_account) = &nonce_account {
2149        Message::new_with_nonce(
2150            ixs,
2151            Some(&fee_payer.pubkey()),
2152            nonce_account,
2153            &nonce_authority.pubkey(),
2154        )
2155    } else {
2156        Message::new(&ixs, Some(&fee_payer.pubkey()))
2157    };
2158    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2159    let mut tx = Transaction::new_unsigned(message);
2160
2161    if sign_only {
2162        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2163        return_signers_with_config(
2164            &tx,
2165            &config.output_format,
2166            &ReturnSignersConfig {
2167                dump_transaction_message,
2168            },
2169        )
2170    } else {
2171        tx.try_sign(&config.signers, recent_blockhash)?;
2172        if let Some(nonce_account) = &nonce_account {
2173            let nonce_account =
2174                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2175                    rpc_client,
2176                    nonce_account,
2177                    config.commitment,
2178                )
2179                .await?;
2180            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2181        }
2182        check_account_for_fee_with_commitment(
2183            rpc_client,
2184            &tx.message.account_keys[0],
2185            &tx.message,
2186            config.commitment,
2187        )
2188        .await?;
2189        let result = rpc_client
2190            .send_and_confirm_transaction_with_spinner_and_config(
2191                &tx,
2192                config.commitment,
2193                config.send_transaction_config,
2194            )
2195            .await;
2196        log_instruction_custom_error::<StakeError>(result, config)
2197    }
2198}
2199
2200#[allow(clippy::too_many_arguments)]
2201pub async fn process_merge_stake(
2202    rpc_client: &RpcClient,
2203    config: &CliConfig<'_>,
2204    stake_account_pubkey: &Pubkey,
2205    source_stake_account_pubkey: &Pubkey,
2206    stake_authority: SignerIndex,
2207    sign_only: bool,
2208    dump_transaction_message: bool,
2209    blockhash_query: &BlockhashQuery,
2210    nonce_account: Option<Pubkey>,
2211    nonce_authority: SignerIndex,
2212    memo: Option<&String>,
2213    fee_payer: SignerIndex,
2214    compute_unit_price: Option<u64>,
2215) -> ProcessResult {
2216    let fee_payer = config.signers[fee_payer];
2217
2218    check_unique_pubkeys(
2219        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2220        (stake_account_pubkey, "stake_account".to_string()),
2221    )?;
2222    check_unique_pubkeys(
2223        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2224        (
2225            source_stake_account_pubkey,
2226            "source_stake_account".to_string(),
2227        ),
2228    )?;
2229    check_unique_pubkeys(
2230        (stake_account_pubkey, "stake_account".to_string()),
2231        (
2232            source_stake_account_pubkey,
2233            "source_stake_account".to_string(),
2234        ),
2235    )?;
2236
2237    let stake_authority = config.signers[stake_authority];
2238
2239    if !sign_only {
2240        for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] {
2241            if let Ok(stake_account) = rpc_client.get_account(stake_account_address).await
2242                && stake_account.owner != stake::program::id()
2243            {
2244                return Err(CliError::BadParameter(format!(
2245                    "Account {stake_account_address} is not a stake account"
2246                ))
2247                .into());
2248            }
2249        }
2250    }
2251
2252    let recent_blockhash = blockhash_query
2253        .get_blockhash(rpc_client, config.commitment)
2254        .await?;
2255
2256    let compute_unit_limit = match blockhash_query {
2257        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2258        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2259    };
2260    let ixs = stake_instruction::merge(
2261        stake_account_pubkey,
2262        source_stake_account_pubkey,
2263        &stake_authority.pubkey(),
2264    )
2265    .with_memo(memo)
2266    .with_compute_unit_config(&ComputeUnitConfig {
2267        compute_unit_price,
2268        compute_unit_limit,
2269    });
2270
2271    let nonce_authority = config.signers[nonce_authority];
2272
2273    let mut message = if let Some(nonce_account) = &nonce_account {
2274        Message::new_with_nonce(
2275            ixs,
2276            Some(&fee_payer.pubkey()),
2277            nonce_account,
2278            &nonce_authority.pubkey(),
2279        )
2280    } else {
2281        Message::new(&ixs, Some(&fee_payer.pubkey()))
2282    };
2283    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2284    let mut tx = Transaction::new_unsigned(message);
2285
2286    if sign_only {
2287        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2288        return_signers_with_config(
2289            &tx,
2290            &config.output_format,
2291            &ReturnSignersConfig {
2292                dump_transaction_message,
2293            },
2294        )
2295    } else {
2296        tx.try_sign(&config.signers, recent_blockhash)?;
2297        if let Some(nonce_account) = &nonce_account {
2298            let nonce_account =
2299                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2300                    rpc_client,
2301                    nonce_account,
2302                    config.commitment,
2303                )
2304                .await?;
2305            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2306        }
2307        check_account_for_fee_with_commitment(
2308            rpc_client,
2309            &tx.message.account_keys[0],
2310            &tx.message,
2311            config.commitment,
2312        )
2313        .await?;
2314        let result = rpc_client
2315            .send_and_confirm_transaction_with_spinner_and_config(
2316                &tx,
2317                config.commitment,
2318                config.send_transaction_config,
2319            )
2320            .await;
2321        log_instruction_custom_error::<StakeError>(result, config)
2322    }
2323}
2324
2325#[allow(clippy::too_many_arguments)]
2326pub async fn process_stake_set_lockup(
2327    rpc_client: &RpcClient,
2328    config: &CliConfig<'_>,
2329    stake_account_pubkey: &Pubkey,
2330    lockup: &LockupArgs,
2331    new_custodian_signer: Option<SignerIndex>,
2332    custodian: SignerIndex,
2333    sign_only: bool,
2334    dump_transaction_message: bool,
2335    blockhash_query: &BlockhashQuery,
2336    nonce_account: Option<Pubkey>,
2337    nonce_authority: SignerIndex,
2338    memo: Option<&String>,
2339    fee_payer: SignerIndex,
2340    compute_unit_price: Option<u64>,
2341) -> ProcessResult {
2342    let recent_blockhash = blockhash_query
2343        .get_blockhash(rpc_client, config.commitment)
2344        .await?;
2345    let custodian = config.signers[custodian];
2346
2347    let compute_unit_limit = match blockhash_query {
2348        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2349        BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2350    };
2351    let ixs = vec![if new_custodian_signer.is_some() {
2352        stake_instruction::set_lockup_checked(stake_account_pubkey, lockup, &custodian.pubkey())
2353    } else {
2354        stake_instruction::set_lockup(stake_account_pubkey, lockup, &custodian.pubkey())
2355    }]
2356    .with_memo(memo)
2357    .with_compute_unit_config(&ComputeUnitConfig {
2358        compute_unit_price,
2359        compute_unit_limit,
2360    });
2361    let nonce_authority = config.signers[nonce_authority];
2362    let fee_payer = config.signers[fee_payer];
2363
2364    if !sign_only {
2365        let state =
2366            get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment).await?;
2367        let lockup = match state {
2368            StakeStateV2::Stake(Meta { lockup, .. }, ..) => Some(lockup),
2369            StakeStateV2::Initialized(Meta { lockup, .. }) => Some(lockup),
2370            _ => None,
2371        };
2372        if let Some(lockup) = lockup {
2373            if lockup.custodian != Pubkey::default() {
2374                check_current_authority(&[lockup.custodian], &custodian.pubkey())?;
2375            }
2376        } else {
2377            return Err(CliError::RpcRequestError(format!(
2378                "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
2379            ))
2380            .into());
2381        }
2382    }
2383
2384    let mut message = if let Some(nonce_account) = &nonce_account {
2385        Message::new_with_nonce(
2386            ixs,
2387            Some(&fee_payer.pubkey()),
2388            nonce_account,
2389            &nonce_authority.pubkey(),
2390        )
2391    } else {
2392        Message::new(&ixs, Some(&fee_payer.pubkey()))
2393    };
2394    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2395    let mut tx = Transaction::new_unsigned(message);
2396
2397    if sign_only {
2398        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2399        return_signers_with_config(
2400            &tx,
2401            &config.output_format,
2402            &ReturnSignersConfig {
2403                dump_transaction_message,
2404            },
2405        )
2406    } else {
2407        tx.try_sign(&config.signers, recent_blockhash)?;
2408        if let Some(nonce_account) = &nonce_account {
2409            let nonce_account =
2410                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2411                    rpc_client,
2412                    nonce_account,
2413                    config.commitment,
2414                )
2415                .await?;
2416            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2417        }
2418        check_account_for_fee_with_commitment(
2419            rpc_client,
2420            &tx.message.account_keys[0],
2421            &tx.message,
2422            config.commitment,
2423        )
2424        .await?;
2425        let result = rpc_client
2426            .send_and_confirm_transaction_with_spinner_and_config(
2427                &tx,
2428                config.commitment,
2429                config.send_transaction_config,
2430            )
2431            .await;
2432        log_instruction_custom_error::<StakeError>(result, config)
2433    }
2434}
2435
2436fn u64_some_if_not_zero(n: u64) -> Option<u64> {
2437    if n > 0 { Some(n) } else { None }
2438}
2439
2440fn stake_activation_status(
2441    delegation: &Delegation,
2442    current_epoch: Epoch,
2443    stake_history: &StakeHistory,
2444    new_rate_activation_epoch: Option<Epoch>,
2445) -> StakeActivationStatus {
2446    delegation.stake_activating_and_deactivating_v2(
2447        current_epoch,
2448        stake_history,
2449        new_rate_activation_epoch,
2450    )
2451}
2452
2453pub fn build_stake_state(
2454    account_balance: u64,
2455    stake_state: &StakeStateV2,
2456    use_lamports_unit: bool,
2457    stake_history: &StakeHistory,
2458    clock: &Clock,
2459    new_rate_activation_epoch: Option<Epoch>,
2460    rent_exempt_reserve: u64,
2461    use_csv: bool,
2462) -> CliStakeState {
2463    match stake_state {
2464        StakeStateV2::Stake(
2465            Meta {
2466                #[expect(deprecated)]
2467                    rent_exempt_reserve: _,
2468                authorized,
2469                lockup,
2470            },
2471            stake,
2472            _,
2473        ) => {
2474            let current_epoch = clock.epoch;
2475            let StakeActivationStatus {
2476                effective,
2477                activating,
2478                deactivating,
2479            } = stake_activation_status(
2480                &stake.delegation,
2481                current_epoch,
2482                stake_history,
2483                new_rate_activation_epoch,
2484            );
2485            let lockup = if lockup.is_in_force(clock, None) {
2486                Some(lockup.into())
2487            } else {
2488                None
2489            };
2490            CliStakeState {
2491                stake_type: CliStakeType::Stake,
2492                account_balance,
2493                credits_observed: Some(stake.credits_observed),
2494                delegated_stake: Some(stake.delegation.stake),
2495                delegated_vote_account_address: if stake.delegation.voter_pubkey
2496                    != Pubkey::default()
2497                {
2498                    Some(stake.delegation.voter_pubkey.to_string())
2499                } else {
2500                    None
2501                },
2502                activation_epoch: Some(if stake.delegation.activation_epoch < u64::MAX {
2503                    stake.delegation.activation_epoch
2504                } else {
2505                    0
2506                }),
2507                deactivation_epoch: if stake.delegation.deactivation_epoch < u64::MAX {
2508                    Some(stake.delegation.deactivation_epoch)
2509                } else {
2510                    None
2511                },
2512                authorized: Some(authorized.into()),
2513                lockup,
2514                use_lamports_unit,
2515                current_epoch,
2516                rent_exempt_reserve: Some(rent_exempt_reserve),
2517                active_stake: u64_some_if_not_zero(effective),
2518                activating_stake: u64_some_if_not_zero(activating),
2519                deactivating_stake: u64_some_if_not_zero(deactivating),
2520                use_csv,
2521                ..CliStakeState::default()
2522            }
2523        }
2524        StakeStateV2::RewardsPool => CliStakeState {
2525            stake_type: CliStakeType::RewardsPool,
2526            account_balance,
2527            ..CliStakeState::default()
2528        },
2529        StakeStateV2::Uninitialized => CliStakeState {
2530            account_balance,
2531            ..CliStakeState::default()
2532        },
2533        StakeStateV2::Initialized(Meta {
2534            #[expect(deprecated)]
2535                rent_exempt_reserve: _,
2536            authorized,
2537            lockup,
2538        }) => {
2539            let lockup = if lockup.is_in_force(clock, None) {
2540                Some(lockup.into())
2541            } else {
2542                None
2543            };
2544            CliStakeState {
2545                stake_type: CliStakeType::Initialized,
2546                account_balance,
2547                credits_observed: Some(0),
2548                authorized: Some(authorized.into()),
2549                lockup,
2550                use_lamports_unit,
2551                rent_exempt_reserve: Some(rent_exempt_reserve),
2552                ..CliStakeState::default()
2553            }
2554        }
2555    }
2556}
2557
2558async fn get_stake_account_state(
2559    rpc_client: &RpcClient,
2560    stake_account_pubkey: &Pubkey,
2561    commitment_config: CommitmentConfig,
2562) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
2563    let stake_account = rpc_client
2564        .get_account_with_commitment(stake_account_pubkey, commitment_config)
2565        .await?
2566        .value
2567        .ok_or_else(|| {
2568            CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
2569        })?;
2570    if stake_account.owner != stake::program::id() {
2571        return Err(CliError::RpcRequestError(format!(
2572            "{stake_account_pubkey:?} is not a stake account",
2573        ))
2574        .into());
2575    }
2576    wincode::deserialize(&stake_account.data).map_err(|err| {
2577        CliError::RpcRequestError(format!(
2578            "Account data could not be deserialized to stake state: {err}"
2579        ))
2580        .into()
2581    })
2582}
2583
2584pub(crate) fn check_current_authority(
2585    permitted_authorities: &[Pubkey],
2586    provided_current_authority: &Pubkey,
2587) -> Result<(), CliError> {
2588    if !permitted_authorities.contains(provided_current_authority) {
2589        Err(CliError::RpcRequestError(format!(
2590            "Invalid authority provided: {provided_current_authority:?}, expected \
2591             {permitted_authorities:?}"
2592        )))
2593    } else {
2594        Ok(())
2595    }
2596}
2597
2598pub async fn get_epoch_boundary_timestamps(
2599    rpc_client: &RpcClient,
2600    reward: &RpcInflationReward,
2601    epoch_schedule: &EpochSchedule,
2602) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
2603    let epoch_end_time = rpc_client.get_block_time(reward.effective_slot).await?;
2604    let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
2605    let epoch_start_time = loop {
2606        if epoch_start_slot >= reward.effective_slot {
2607            return Err("epoch_start_time not found".to_string().into());
2608        }
2609        match rpc_client.get_block_time(epoch_start_slot).await {
2610            Ok(block_time) => {
2611                break block_time;
2612            }
2613            Err(_) => {
2614                // TODO This is wrong.  We should not just increase the slot index if the RPC
2615                // request failed.  It could have failed for a number of reasons, including, for
2616                // example a network failure.
2617                epoch_start_slot = epoch_start_slot
2618                    .checked_add(1)
2619                    .ok_or("Reached last slot that fits into u64")?;
2620            }
2621        }
2622    };
2623    Ok((epoch_start_time, epoch_end_time))
2624}
2625
2626pub fn make_cli_reward(
2627    reward: &RpcInflationReward,
2628    block_time: UnixTimestamp,
2629    epoch_start_time: UnixTimestamp,
2630    epoch_end_time: UnixTimestamp,
2631) -> Option<CliEpochReward> {
2632    let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
2633    if reward.post_balance > reward.amount {
2634        let rate_change =
2635            reward.amount as f64 / (reward.post_balance.saturating_sub(reward.amount)) as f64;
2636
2637        let wallclock_epochs_per_year =
2638            (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
2639        let apr = rate_change * wallclock_epochs_per_year;
2640
2641        Some(CliEpochReward {
2642            epoch: reward.epoch,
2643            effective_slot: reward.effective_slot,
2644            amount: reward.amount,
2645            post_balance: reward.post_balance,
2646            percent_change: rate_change * 100.0,
2647            apr: Some(apr * 100.0),
2648            commission: reward.commission,
2649            block_time,
2650        })
2651    } else {
2652        None
2653    }
2654}
2655
2656pub(crate) async fn fetch_epoch_rewards(
2657    rpc_client: &RpcClient,
2658    address: &Pubkey,
2659    mut num_epochs: usize,
2660    starting_epoch: Option<u64>,
2661) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
2662    let mut all_epoch_rewards = vec![];
2663    let epoch_schedule = rpc_client.get_epoch_schedule().await?;
2664    let mut rewards_epoch = if let Some(epoch) = starting_epoch {
2665        epoch
2666    } else {
2667        rpc_client
2668            .get_epoch_info()
2669            .await?
2670            .epoch
2671            .saturating_sub(num_epochs as u64)
2672    };
2673
2674    while num_epochs > 0 {
2675        if let Ok(rewards) = rpc_client
2676            .get_inflation_reward(&[*address], Some(rewards_epoch))
2677            .await
2678        {
2679            if let Some(reward) = &rewards[0] {
2680                let (epoch_start_time, epoch_end_time) =
2681                    get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule).await?;
2682                let block_time = rpc_client.get_block_time(reward.effective_slot).await?;
2683                if let Some(cli_reward) =
2684                    make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
2685                {
2686                    all_epoch_rewards.push(cli_reward);
2687                }
2688            }
2689        } else {
2690            eprintln!("Rewards not available for epoch {rewards_epoch}");
2691        }
2692        num_epochs = num_epochs.saturating_sub(1);
2693        rewards_epoch = rewards_epoch.saturating_add(1);
2694    }
2695
2696    Ok(all_epoch_rewards)
2697}
2698
2699pub async fn process_show_stake_account(
2700    rpc_client: &RpcClient,
2701    config: &CliConfig<'_>,
2702    stake_account_address: &Pubkey,
2703    use_lamports_unit: bool,
2704    with_rewards: Option<usize>,
2705    use_csv: bool,
2706    starting_epoch: Option<u64>,
2707) -> ProcessResult {
2708    let stake_account = rpc_client.get_account(stake_account_address).await?;
2709    let state = get_account_stake_state(
2710        rpc_client,
2711        stake_account_address,
2712        stake_account,
2713        use_lamports_unit,
2714        with_rewards,
2715        use_csv,
2716        starting_epoch,
2717    )
2718    .await?;
2719    Ok(config.output_format.formatted_string(&state))
2720}
2721
2722pub async fn get_account_stake_state(
2723    rpc_client: &RpcClient,
2724    stake_account_address: &Pubkey,
2725    stake_account: solana_account::Account,
2726    use_lamports_unit: bool,
2727    with_rewards: Option<usize>,
2728    use_csv: bool,
2729    starting_epoch: Option<u64>,
2730) -> Result<CliStakeState, CliError> {
2731    if stake_account.owner != stake::program::id() {
2732        return Err(CliError::RpcRequestError(format!(
2733            "{stake_account_address:?} is not a stake account",
2734        )));
2735    }
2736    match wincode::deserialize::<StakeStateV2>(&stake_account.data) {
2737        Ok(stake_state) => {
2738            let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2739            let stake_history: StakeHistory = wincode::deserialize(&stake_history_account.data)
2740                .map_err(|_| {
2741                    CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2742                })?;
2743            let clock_account = rpc_client.get_account(&clock::id()).await?;
2744            let clock: Clock = wincode::deserialize(&clock_account.data).map_err(|_| {
2745                CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
2746            })?;
2747            let new_rate_activation_epoch = get_feature_activation_epoch(
2748                rpc_client,
2749                &agave_feature_set::reduce_stake_warmup_cooldown::id(),
2750            )
2751            .await?;
2752            let rent_exempt_balance = rpc_client
2753                .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2754                .await?;
2755            let mut state = build_stake_state(
2756                stake_account.lamports,
2757                &stake_state,
2758                use_lamports_unit,
2759                &stake_history,
2760                &clock,
2761                new_rate_activation_epoch,
2762                rent_exempt_balance,
2763                use_csv,
2764            );
2765
2766            if state.stake_type == CliStakeType::Stake
2767                && state.activation_epoch.is_some()
2768                && let Some(num_epochs) = with_rewards
2769            {
2770                state.epoch_rewards = match fetch_epoch_rewards(
2771                    rpc_client,
2772                    stake_account_address,
2773                    num_epochs,
2774                    starting_epoch,
2775                )
2776                .await
2777                {
2778                    Ok(rewards) => Some(rewards),
2779                    Err(error) => {
2780                        eprintln!("Failed to fetch epoch rewards: {error:?}");
2781                        None
2782                    }
2783                };
2784            }
2785            Ok(state)
2786        }
2787        Err(err) => Err(CliError::RpcRequestError(format!(
2788            "Account data could not be deserialized to stake state: {err}"
2789        ))),
2790    }
2791}
2792
2793pub async fn process_show_stake_history(
2794    rpc_client: &RpcClient,
2795    config: &CliConfig<'_>,
2796    use_lamports_unit: bool,
2797    limit_results: usize,
2798) -> ProcessResult {
2799    let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2800    let stake_history =
2801        wincode::deserialize::<StakeHistory>(&stake_history_account.data).map_err(|_| {
2802            CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2803        })?;
2804
2805    let limit_results = match config.output_format {
2806        OutputFormat::Json | OutputFormat::JsonCompact => usize::MAX,
2807        _ => {
2808            if limit_results == 0 {
2809                usize::MAX
2810            } else {
2811                limit_results
2812            }
2813        }
2814    };
2815    let mut entries: Vec<CliStakeHistoryEntry> = vec![];
2816    for entry in stake_history.deref().iter().take(limit_results) {
2817        entries.push(entry.into());
2818    }
2819    let stake_history_output = CliStakeHistory {
2820        entries,
2821        use_lamports_unit,
2822    };
2823    Ok(config.output_format.formatted_string(&stake_history_output))
2824}
2825
2826#[allow(clippy::too_many_arguments)]
2827pub async fn process_delegate_stake(
2828    rpc_client: &RpcClient,
2829    config: &CliConfig<'_>,
2830    stake_account_pubkey: &Pubkey,
2831    vote_account_pubkey: &Pubkey,
2832    stake_authority: SignerIndex,
2833    force: bool,
2834    sign_only: bool,
2835    dump_transaction_message: bool,
2836    blockhash_query: &BlockhashQuery,
2837    nonce_account: Option<Pubkey>,
2838    nonce_authority: SignerIndex,
2839    memo: Option<&String>,
2840    fee_payer: SignerIndex,
2841    compute_unit_price: Option<u64>,
2842) -> ProcessResult {
2843    check_unique_pubkeys(
2844        (&config.signers[0].pubkey(), "cli keypair".to_string()),
2845        (stake_account_pubkey, "stake_account_pubkey".to_string()),
2846    )?;
2847    let stake_authority = config.signers[stake_authority];
2848
2849    if !sign_only {
2850        // Sanity check the vote account to ensure it is attached to a validator that has recently
2851        // voted at the tip of the ledger
2852        let get_vote_accounts_config = RpcGetVoteAccountsConfig {
2853            vote_pubkey: Some(vote_account_pubkey.to_string()),
2854            keep_unstaked_delinquents: Some(true),
2855            commitment: Some(rpc_client.commitment()),
2856            ..RpcGetVoteAccountsConfig::default()
2857        };
2858        let RpcVoteAccountStatus {
2859            current,
2860            delinquent,
2861        } = rpc_client
2862            .get_vote_accounts_with_config(get_vote_accounts_config)
2863            .await?;
2864        // filter should return at most one result
2865        let rpc_vote_account =
2866            current
2867                .first()
2868                .or_else(|| delinquent.first())
2869                .ok_or(CliError::RpcRequestError(format!(
2870                    "Vote account not found: {vote_account_pubkey}"
2871                )))?;
2872
2873        let activated_stake = rpc_vote_account.activated_stake;
2874        let root_slot = rpc_vote_account.root_slot;
2875        let min_root_slot = rpc_client
2876            .get_slot()
2877            .await
2878            .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
2879        let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
2880            Ok(())
2881        } else if root_slot == 0 {
2882            Err(CliError::BadParameter(
2883                "Unable to delegate. Vote account has no root slot".to_string(),
2884            ))
2885        } else {
2886            Err(CliError::DynamicProgramError(format!(
2887                "Unable to delegate.  Vote account appears delinquent because its current root \
2888                 slot, {root_slot}, is less than {min_root_slot}"
2889            )))
2890        };
2891
2892        if let Err(err) = &sanity_check_result {
2893            if !force {
2894                sanity_check_result?;
2895            } else {
2896                writeln_stdout(format_args!("--force supplied, ignoring: {err}"))?;
2897            }
2898        }
2899
2900        if !force {
2901            let stake_account = rpc_client.get_account(stake_account_pubkey).await?;
2902            if stake_account.owner != stake::program::id() {
2903                return Err(CliError::BadParameter(format!(
2904                    "{stake_account_pubkey} is not a stake account",
2905                ))
2906                .into());
2907            }
2908
2909            let rent_exempt_balance = rpc_client
2910                .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2911                .await?;
2912            let minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2913            let minimum_total_lamports = rent_exempt_balance.saturating_add(minimum_delegation);
2914            let stake_account_lamports = stake_account.lamports;
2915
2916            if stake_account_lamports < minimum_total_lamports {
2917                return Err(CliError::BadParameter(format!(
2918                    "need at least {minimum_total_lamports} lamports to delegate this stake \
2919                     account, available lamports: {stake_account_lamports}"
2920                ))
2921                .into());
2922            }
2923        }
2924    }
2925
2926    let recent_blockhash = blockhash_query
2927        .get_blockhash(rpc_client, config.commitment)
2928        .await?;
2929
2930    // DelegateStake parses a VoteState, which may change between simulation and execution
2931    let compute_unit_limit = match blockhash_query {
2932        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2933        BlockhashQuery::Rpc(_) => ComputeUnitLimit::SimulatedWithExtraPercentage(5),
2934    };
2935    let ixs = vec![stake_instruction::delegate_stake(
2936        stake_account_pubkey,
2937        &stake_authority.pubkey(),
2938        vote_account_pubkey,
2939    )]
2940    .with_memo(memo)
2941    .with_compute_unit_config(&ComputeUnitConfig {
2942        compute_unit_price,
2943        compute_unit_limit,
2944    });
2945
2946    let nonce_authority = config.signers[nonce_authority];
2947    let fee_payer = config.signers[fee_payer];
2948
2949    let mut message = if let Some(nonce_account) = &nonce_account {
2950        Message::new_with_nonce(
2951            ixs,
2952            Some(&fee_payer.pubkey()),
2953            nonce_account,
2954            &nonce_authority.pubkey(),
2955        )
2956    } else {
2957        Message::new(&ixs, Some(&fee_payer.pubkey()))
2958    };
2959    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2960    let mut tx = Transaction::new_unsigned(message);
2961
2962    if sign_only {
2963        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2964        return_signers_with_config(
2965            &tx,
2966            &config.output_format,
2967            &ReturnSignersConfig {
2968                dump_transaction_message,
2969            },
2970        )
2971    } else {
2972        tx.try_sign(&config.signers, recent_blockhash)?;
2973        if let Some(nonce_account) = &nonce_account {
2974            let nonce_account =
2975                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2976                    rpc_client,
2977                    nonce_account,
2978                    config.commitment,
2979                )
2980                .await?;
2981            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2982        }
2983        check_account_for_fee_with_commitment(
2984            rpc_client,
2985            &tx.message.account_keys[0],
2986            &tx.message,
2987            config.commitment,
2988        )
2989        .await?;
2990        let result = rpc_client
2991            .send_and_confirm_transaction_with_spinner_and_config(
2992                &tx,
2993                config.commitment,
2994                config.send_transaction_config,
2995            )
2996            .await;
2997        log_instruction_custom_error::<StakeError>(result, config)
2998    }
2999}
3000
3001pub async fn process_stake_minimum_delegation(
3002    rpc_client: &RpcClient,
3003    config: &CliConfig<'_>,
3004    use_lamports_unit: bool,
3005) -> ProcessResult {
3006    let stake_minimum_delegation = rpc_client
3007        .get_stake_minimum_delegation_with_commitment(config.commitment)
3008        .await?;
3009
3010    let stake_minimum_delegation_output = CliBalance {
3011        lamports: stake_minimum_delegation,
3012        config: BuildBalanceMessageConfig {
3013            use_lamports_unit,
3014            show_unit: true,
3015            trim_trailing_zeros: true,
3016        },
3017    };
3018
3019    Ok(config
3020        .output_format
3021        .formatted_string(&stake_minimum_delegation_output))
3022}
3023
3024#[cfg(test)]
3025mod tests {
3026    use {
3027        super::*,
3028        crate::{clap_app::get_clap_app, cli::parse_command},
3029        solana_hash::Hash,
3030        solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair},
3031        solana_presigner::Presigner,
3032        solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
3033        solana_signer::Signer,
3034        tempfile::NamedTempFile,
3035    };
3036
3037    fn make_tmp_file() -> (String, NamedTempFile) {
3038        let tmp_file = NamedTempFile::new().unwrap();
3039        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
3040    }
3041
3042    #[test]
3043    #[allow(clippy::cognitive_complexity)]
3044    fn test_parse_command() {
3045        let test_commands = get_clap_app("test", "desc", "version");
3046        let default_keypair = Keypair::new();
3047        let (default_keypair_file, mut tmp_file) = make_tmp_file();
3048        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
3049        let default_signer = DefaultSigner::new("", &default_keypair_file);
3050        let (keypair_file, mut tmp_file) = make_tmp_file();
3051        let stake_account_keypair = Keypair::new();
3052        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
3053        let stake_account_pubkey = stake_account_keypair.pubkey();
3054        let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
3055        let stake_authority_keypair = Keypair::new();
3056        write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
3057        let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
3058        let custodian_keypair = Keypair::new();
3059        write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
3060
3061        // stake-authorize subcommand
3062        let stake_account_string = stake_account_pubkey.to_string();
3063        let new_stake_authority = Pubkey::from([1u8; 32]);
3064        let new_stake_string = new_stake_authority.to_string();
3065        let new_withdraw_authority = Pubkey::from([2u8; 32]);
3066        let new_withdraw_string = new_withdraw_authority.to_string();
3067        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3068            "test",
3069            "stake-authorize",
3070            &stake_account_string,
3071            "--new-stake-authority",
3072            &new_stake_string,
3073            "--new-withdraw-authority",
3074            &new_withdraw_string,
3075        ]);
3076        assert_eq!(
3077            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3078            CliCommandInfo {
3079                command: CliCommand::StakeAuthorize {
3080                    stake_account_pubkey,
3081                    new_authorizations: vec![
3082                        StakeAuthorizationIndexed {
3083                            authorization_type: StakeAuthorize::Staker,
3084                            new_authority_pubkey: new_stake_authority,
3085                            authority: 0,
3086                            new_authority_signer: None,
3087                        },
3088                        StakeAuthorizationIndexed {
3089                            authorization_type: StakeAuthorize::Withdrawer,
3090                            new_authority_pubkey: new_withdraw_authority,
3091                            authority: 0,
3092                            new_authority_signer: None,
3093                        },
3094                    ],
3095                    sign_only: false,
3096                    dump_transaction_message: false,
3097                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3098                    nonce_account: None,
3099                    nonce_authority: 0,
3100                    memo: None,
3101                    fee_payer: 0,
3102                    custodian: None,
3103                    no_wait: false,
3104                    compute_unit_price: None,
3105                },
3106                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3107            },
3108        );
3109        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3110        let withdraw_authority_keypair = Keypair::new();
3111        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3112        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3113            "test",
3114            "stake-authorize",
3115            &stake_account_string,
3116            "--new-stake-authority",
3117            &new_stake_string,
3118            "--new-withdraw-authority",
3119            &new_withdraw_string,
3120            "--stake-authority",
3121            &stake_authority_keypair_file,
3122            "--withdraw-authority",
3123            &withdraw_authority_keypair_file,
3124        ]);
3125        assert_eq!(
3126            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3127            CliCommandInfo {
3128                command: CliCommand::StakeAuthorize {
3129                    stake_account_pubkey,
3130                    new_authorizations: vec![
3131                        StakeAuthorizationIndexed {
3132                            authorization_type: StakeAuthorize::Staker,
3133                            new_authority_pubkey: new_stake_authority,
3134                            authority: 1,
3135                            new_authority_signer: None,
3136                        },
3137                        StakeAuthorizationIndexed {
3138                            authorization_type: StakeAuthorize::Withdrawer,
3139                            new_authority_pubkey: new_withdraw_authority,
3140                            authority: 2,
3141                            new_authority_signer: None,
3142                        },
3143                    ],
3144                    sign_only: false,
3145                    dump_transaction_message: false,
3146                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3147                    nonce_account: None,
3148                    nonce_authority: 0,
3149                    memo: None,
3150                    fee_payer: 0,
3151                    custodian: None,
3152                    no_wait: false,
3153                    compute_unit_price: None,
3154                },
3155                signers: vec![
3156                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3157                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3158                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3159                ],
3160            },
3161        );
3162        // Withdraw authority may set both new authorities
3163        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3164            "test",
3165            "stake-authorize",
3166            &stake_account_string,
3167            "--new-stake-authority",
3168            &new_stake_string,
3169            "--new-withdraw-authority",
3170            &new_withdraw_string,
3171            "--withdraw-authority",
3172            &withdraw_authority_keypair_file,
3173        ]);
3174        assert_eq!(
3175            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3176            CliCommandInfo {
3177                command: CliCommand::StakeAuthorize {
3178                    stake_account_pubkey,
3179                    new_authorizations: vec![
3180                        StakeAuthorizationIndexed {
3181                            authorization_type: StakeAuthorize::Staker,
3182                            new_authority_pubkey: new_stake_authority,
3183                            authority: 1,
3184                            new_authority_signer: None,
3185                        },
3186                        StakeAuthorizationIndexed {
3187                            authorization_type: StakeAuthorize::Withdrawer,
3188                            new_authority_pubkey: new_withdraw_authority,
3189                            authority: 1,
3190                            new_authority_signer: None,
3191                        },
3192                    ],
3193                    sign_only: false,
3194                    dump_transaction_message: false,
3195                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3196                    nonce_account: None,
3197                    nonce_authority: 0,
3198                    memo: None,
3199                    fee_payer: 0,
3200                    custodian: None,
3201                    no_wait: false,
3202                    compute_unit_price: None,
3203                },
3204                signers: vec![
3205                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3206                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3207                ],
3208            },
3209        );
3210        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3211            "test",
3212            "stake-authorize",
3213            &stake_account_string,
3214            "--new-stake-authority",
3215            &new_stake_string,
3216        ]);
3217        assert_eq!(
3218            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3219            CliCommandInfo {
3220                command: CliCommand::StakeAuthorize {
3221                    stake_account_pubkey,
3222                    new_authorizations: vec![StakeAuthorizationIndexed {
3223                        authorization_type: StakeAuthorize::Staker,
3224                        new_authority_pubkey: new_stake_authority,
3225                        authority: 0,
3226                        new_authority_signer: None,
3227                    }],
3228                    sign_only: false,
3229                    dump_transaction_message: false,
3230                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3231                    nonce_account: None,
3232                    nonce_authority: 0,
3233                    memo: None,
3234                    fee_payer: 0,
3235                    custodian: None,
3236                    no_wait: false,
3237                    compute_unit_price: None,
3238                },
3239                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3240            },
3241        );
3242        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3243            "test",
3244            "stake-authorize",
3245            &stake_account_string,
3246            "--new-stake-authority",
3247            &new_stake_string,
3248            "--stake-authority",
3249            &stake_authority_keypair_file,
3250        ]);
3251        assert_eq!(
3252            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3253            CliCommandInfo {
3254                command: CliCommand::StakeAuthorize {
3255                    stake_account_pubkey,
3256                    new_authorizations: vec![StakeAuthorizationIndexed {
3257                        authorization_type: StakeAuthorize::Staker,
3258                        new_authority_pubkey: new_stake_authority,
3259                        authority: 1,
3260                        new_authority_signer: None,
3261                    }],
3262                    sign_only: false,
3263                    dump_transaction_message: false,
3264                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3265                    nonce_account: None,
3266                    nonce_authority: 0,
3267                    memo: None,
3268                    fee_payer: 0,
3269                    custodian: None,
3270                    no_wait: false,
3271                    compute_unit_price: None,
3272                },
3273                signers: vec![
3274                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3275                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3276                ],
3277            },
3278        );
3279        // Withdraw authority may set new stake authority
3280        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3281            "test",
3282            "stake-authorize",
3283            &stake_account_string,
3284            "--new-stake-authority",
3285            &new_stake_string,
3286            "--withdraw-authority",
3287            &withdraw_authority_keypair_file,
3288        ]);
3289        assert_eq!(
3290            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3291            CliCommandInfo {
3292                command: CliCommand::StakeAuthorize {
3293                    stake_account_pubkey,
3294                    new_authorizations: vec![StakeAuthorizationIndexed {
3295                        authorization_type: StakeAuthorize::Staker,
3296                        new_authority_pubkey: new_stake_authority,
3297                        authority: 1,
3298                        new_authority_signer: None,
3299                    }],
3300                    sign_only: false,
3301                    dump_transaction_message: false,
3302                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3303                    nonce_account: None,
3304                    nonce_authority: 0,
3305                    memo: None,
3306                    fee_payer: 0,
3307                    custodian: None,
3308                    no_wait: false,
3309                    compute_unit_price: None,
3310                },
3311                signers: vec![
3312                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3313                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3314                ],
3315            },
3316        );
3317        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3318            "test",
3319            "stake-authorize",
3320            &stake_account_string,
3321            "--new-withdraw-authority",
3322            &new_withdraw_string,
3323        ]);
3324        assert_eq!(
3325            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3326            CliCommandInfo {
3327                command: CliCommand::StakeAuthorize {
3328                    stake_account_pubkey,
3329                    new_authorizations: vec![StakeAuthorizationIndexed {
3330                        authorization_type: StakeAuthorize::Withdrawer,
3331                        new_authority_pubkey: new_withdraw_authority,
3332                        authority: 0,
3333                        new_authority_signer: None,
3334                    }],
3335                    sign_only: false,
3336                    dump_transaction_message: false,
3337                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3338                    nonce_account: None,
3339                    nonce_authority: 0,
3340                    memo: None,
3341                    fee_payer: 0,
3342                    custodian: None,
3343                    no_wait: false,
3344                    compute_unit_price: None,
3345                },
3346                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3347            },
3348        );
3349        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3350            "test",
3351            "stake-authorize",
3352            &stake_account_string,
3353            "--new-withdraw-authority",
3354            &new_withdraw_string,
3355            "--withdraw-authority",
3356            &withdraw_authority_keypair_file,
3357        ]);
3358        assert_eq!(
3359            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3360            CliCommandInfo {
3361                command: CliCommand::StakeAuthorize {
3362                    stake_account_pubkey,
3363                    new_authorizations: vec![StakeAuthorizationIndexed {
3364                        authorization_type: StakeAuthorize::Withdrawer,
3365                        new_authority_pubkey: new_withdraw_authority,
3366                        authority: 1,
3367                        new_authority_signer: None,
3368                    }],
3369                    sign_only: false,
3370                    dump_transaction_message: false,
3371                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3372                    nonce_account: None,
3373                    nonce_authority: 0,
3374                    memo: None,
3375                    fee_payer: 0,
3376                    custodian: None,
3377                    no_wait: false,
3378                    compute_unit_price: None,
3379                },
3380                signers: vec![
3381                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3382                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3383                ],
3384            },
3385        );
3386
3387        // Test Authorize Subcommand w/ no-wait
3388        let test_authorize = test_commands.clone().get_matches_from(vec![
3389            "test",
3390            "stake-authorize",
3391            &stake_account_string,
3392            "--new-stake-authority",
3393            &stake_account_string,
3394            "--no-wait",
3395        ]);
3396        assert_eq!(
3397            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3398            CliCommandInfo {
3399                command: CliCommand::StakeAuthorize {
3400                    stake_account_pubkey,
3401                    new_authorizations: vec![StakeAuthorizationIndexed {
3402                        authorization_type: StakeAuthorize::Staker,
3403                        new_authority_pubkey: stake_account_pubkey,
3404                        authority: 0,
3405                        new_authority_signer: None,
3406                    }],
3407                    sign_only: false,
3408                    dump_transaction_message: false,
3409                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3410                    nonce_account: None,
3411                    nonce_authority: 0,
3412                    memo: None,
3413                    fee_payer: 0,
3414                    custodian: None,
3415                    no_wait: true,
3416                    compute_unit_price: None,
3417                },
3418                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3419            }
3420        );
3421
3422        // stake-authorize-checked subcommand
3423        let (authority_keypair_file, mut tmp_file) = make_tmp_file();
3424        let authority_keypair = Keypair::new();
3425        write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
3426        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3427            "test",
3428            "stake-authorize-checked",
3429            &stake_account_string,
3430            "--new-stake-authority",
3431            &authority_keypair_file,
3432            "--new-withdraw-authority",
3433            &authority_keypair_file,
3434        ]);
3435        assert_eq!(
3436            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3437            CliCommandInfo {
3438                command: CliCommand::StakeAuthorize {
3439                    stake_account_pubkey,
3440                    new_authorizations: vec![
3441                        StakeAuthorizationIndexed {
3442                            authorization_type: StakeAuthorize::Staker,
3443                            new_authority_pubkey: authority_keypair.pubkey(),
3444                            authority: 0,
3445                            new_authority_signer: Some(1),
3446                        },
3447                        StakeAuthorizationIndexed {
3448                            authorization_type: StakeAuthorize::Withdrawer,
3449                            new_authority_pubkey: authority_keypair.pubkey(),
3450                            authority: 0,
3451                            new_authority_signer: Some(1),
3452                        },
3453                    ],
3454                    sign_only: false,
3455                    dump_transaction_message: false,
3456                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3457                    nonce_account: None,
3458                    nonce_authority: 0,
3459                    memo: None,
3460                    fee_payer: 0,
3461                    custodian: None,
3462                    no_wait: false,
3463                    compute_unit_price: None,
3464                },
3465                signers: vec![
3466                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3467                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3468                ],
3469            },
3470        );
3471        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3472        let withdraw_authority_keypair = Keypair::new();
3473        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3474        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3475            "test",
3476            "stake-authorize-checked",
3477            &stake_account_string,
3478            "--new-stake-authority",
3479            &authority_keypair_file,
3480            "--new-withdraw-authority",
3481            &authority_keypair_file,
3482            "--stake-authority",
3483            &stake_authority_keypair_file,
3484            "--withdraw-authority",
3485            &withdraw_authority_keypair_file,
3486        ]);
3487        assert_eq!(
3488            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3489            CliCommandInfo {
3490                command: CliCommand::StakeAuthorize {
3491                    stake_account_pubkey,
3492                    new_authorizations: vec![
3493                        StakeAuthorizationIndexed {
3494                            authorization_type: StakeAuthorize::Staker,
3495                            new_authority_pubkey: authority_keypair.pubkey(),
3496                            authority: 1,
3497                            new_authority_signer: Some(2),
3498                        },
3499                        StakeAuthorizationIndexed {
3500                            authorization_type: StakeAuthorize::Withdrawer,
3501                            new_authority_pubkey: authority_keypair.pubkey(),
3502                            authority: 3,
3503                            new_authority_signer: Some(2),
3504                        },
3505                    ],
3506                    sign_only: false,
3507                    dump_transaction_message: false,
3508                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3509                    nonce_account: None,
3510                    nonce_authority: 0,
3511                    memo: None,
3512                    fee_payer: 0,
3513                    custodian: None,
3514                    no_wait: false,
3515                    compute_unit_price: None,
3516                },
3517                signers: vec![
3518                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3519                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3520                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3521                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3522                ],
3523            },
3524        );
3525        // Withdraw authority may set both new authorities
3526        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3527            "test",
3528            "stake-authorize-checked",
3529            &stake_account_string,
3530            "--new-stake-authority",
3531            &authority_keypair_file,
3532            "--new-withdraw-authority",
3533            &authority_keypair_file,
3534            "--withdraw-authority",
3535            &withdraw_authority_keypair_file,
3536        ]);
3537        assert_eq!(
3538            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3539            CliCommandInfo {
3540                command: CliCommand::StakeAuthorize {
3541                    stake_account_pubkey,
3542                    new_authorizations: vec![
3543                        StakeAuthorizationIndexed {
3544                            authorization_type: StakeAuthorize::Staker,
3545                            new_authority_pubkey: authority_keypair.pubkey(),
3546                            authority: 1,
3547                            new_authority_signer: Some(2),
3548                        },
3549                        StakeAuthorizationIndexed {
3550                            authorization_type: StakeAuthorize::Withdrawer,
3551                            new_authority_pubkey: authority_keypair.pubkey(),
3552                            authority: 1,
3553                            new_authority_signer: Some(2),
3554                        },
3555                    ],
3556                    sign_only: false,
3557                    dump_transaction_message: false,
3558                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3559                    nonce_account: None,
3560                    nonce_authority: 0,
3561                    memo: None,
3562                    fee_payer: 0,
3563                    custodian: None,
3564                    no_wait: false,
3565                    compute_unit_price: None,
3566                },
3567                signers: vec![
3568                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3569                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3570                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3571                ],
3572            },
3573        );
3574        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3575            "test",
3576            "stake-authorize-checked",
3577            &stake_account_string,
3578            "--new-stake-authority",
3579            &authority_keypair_file,
3580        ]);
3581        assert_eq!(
3582            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3583            CliCommandInfo {
3584                command: CliCommand::StakeAuthorize {
3585                    stake_account_pubkey,
3586                    new_authorizations: vec![StakeAuthorizationIndexed {
3587                        authorization_type: StakeAuthorize::Staker,
3588                        new_authority_pubkey: authority_keypair.pubkey(),
3589                        authority: 0,
3590                        new_authority_signer: Some(1),
3591                    }],
3592                    sign_only: false,
3593                    dump_transaction_message: false,
3594                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3595                    nonce_account: None,
3596                    nonce_authority: 0,
3597                    memo: None,
3598                    fee_payer: 0,
3599                    custodian: None,
3600                    no_wait: false,
3601                    compute_unit_price: None,
3602                },
3603                signers: vec![
3604                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3605                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3606                ],
3607            },
3608        );
3609        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3610            "test",
3611            "stake-authorize-checked",
3612            &stake_account_string,
3613            "--new-stake-authority",
3614            &authority_keypair_file,
3615            "--stake-authority",
3616            &stake_authority_keypair_file,
3617        ]);
3618        assert_eq!(
3619            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3620            CliCommandInfo {
3621                command: CliCommand::StakeAuthorize {
3622                    stake_account_pubkey,
3623                    new_authorizations: vec![StakeAuthorizationIndexed {
3624                        authorization_type: StakeAuthorize::Staker,
3625                        new_authority_pubkey: authority_keypair.pubkey(),
3626                        authority: 1,
3627                        new_authority_signer: Some(2),
3628                    }],
3629                    sign_only: false,
3630                    dump_transaction_message: false,
3631                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3632                    nonce_account: None,
3633                    nonce_authority: 0,
3634                    memo: None,
3635                    fee_payer: 0,
3636                    custodian: None,
3637                    no_wait: false,
3638                    compute_unit_price: None,
3639                },
3640                signers: vec![
3641                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3642                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3643                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3644                ],
3645            },
3646        );
3647        // Withdraw authority may set new stake authority
3648        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3649            "test",
3650            "stake-authorize-checked",
3651            &stake_account_string,
3652            "--new-stake-authority",
3653            &authority_keypair_file,
3654            "--withdraw-authority",
3655            &withdraw_authority_keypair_file,
3656        ]);
3657        assert_eq!(
3658            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3659            CliCommandInfo {
3660                command: CliCommand::StakeAuthorize {
3661                    stake_account_pubkey,
3662                    new_authorizations: vec![StakeAuthorizationIndexed {
3663                        authorization_type: StakeAuthorize::Staker,
3664                        new_authority_pubkey: authority_keypair.pubkey(),
3665                        authority: 1,
3666                        new_authority_signer: Some(2),
3667                    }],
3668                    sign_only: false,
3669                    dump_transaction_message: false,
3670                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3671                    nonce_account: None,
3672                    nonce_authority: 0,
3673                    memo: None,
3674                    fee_payer: 0,
3675                    custodian: None,
3676                    no_wait: false,
3677                    compute_unit_price: None,
3678                },
3679                signers: vec![
3680                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3681                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3682                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3683                ],
3684            },
3685        );
3686        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3687            "test",
3688            "stake-authorize-checked",
3689            &stake_account_string,
3690            "--new-withdraw-authority",
3691            &authority_keypair_file,
3692        ]);
3693        assert_eq!(
3694            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3695            CliCommandInfo {
3696                command: CliCommand::StakeAuthorize {
3697                    stake_account_pubkey,
3698                    new_authorizations: vec![StakeAuthorizationIndexed {
3699                        authorization_type: StakeAuthorize::Withdrawer,
3700                        new_authority_pubkey: authority_keypair.pubkey(),
3701                        authority: 0,
3702                        new_authority_signer: Some(1),
3703                    }],
3704                    sign_only: false,
3705                    dump_transaction_message: false,
3706                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3707                    nonce_account: None,
3708                    nonce_authority: 0,
3709                    memo: None,
3710                    fee_payer: 0,
3711                    custodian: None,
3712                    no_wait: false,
3713                    compute_unit_price: None,
3714                },
3715                signers: vec![
3716                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3717                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3718                ],
3719            },
3720        );
3721        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3722            "test",
3723            "stake-authorize-checked",
3724            &stake_account_string,
3725            "--new-withdraw-authority",
3726            &authority_keypair_file,
3727            "--withdraw-authority",
3728            &withdraw_authority_keypair_file,
3729        ]);
3730        assert_eq!(
3731            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3732            CliCommandInfo {
3733                command: CliCommand::StakeAuthorize {
3734                    stake_account_pubkey,
3735                    new_authorizations: vec![StakeAuthorizationIndexed {
3736                        authorization_type: StakeAuthorize::Withdrawer,
3737                        new_authority_pubkey: authority_keypair.pubkey(),
3738                        authority: 1,
3739                        new_authority_signer: Some(2),
3740                    }],
3741                    sign_only: false,
3742                    dump_transaction_message: false,
3743                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3744                    nonce_account: None,
3745                    nonce_authority: 0,
3746                    memo: None,
3747                    fee_payer: 0,
3748                    custodian: None,
3749                    no_wait: false,
3750                    compute_unit_price: None,
3751                },
3752                signers: vec![
3753                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3754                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3755                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3756                ],
3757            },
3758        );
3759
3760        // Test Authorize Subcommand w/ no-wait
3761        let test_authorize = test_commands.clone().get_matches_from(vec![
3762            "test",
3763            "stake-authorize-checked",
3764            &stake_account_string,
3765            "--new-stake-authority",
3766            &authority_keypair_file,
3767            "--no-wait",
3768        ]);
3769        assert_eq!(
3770            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3771            CliCommandInfo {
3772                command: CliCommand::StakeAuthorize {
3773                    stake_account_pubkey,
3774                    new_authorizations: vec![StakeAuthorizationIndexed {
3775                        authorization_type: StakeAuthorize::Staker,
3776                        new_authority_pubkey: authority_keypair.pubkey(),
3777                        authority: 0,
3778                        new_authority_signer: Some(1),
3779                    }],
3780                    sign_only: false,
3781                    dump_transaction_message: false,
3782                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3783                    nonce_account: None,
3784                    nonce_authority: 0,
3785                    memo: None,
3786                    fee_payer: 0,
3787                    custodian: None,
3788                    no_wait: true,
3789                    compute_unit_price: None,
3790                },
3791                signers: vec![
3792                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3793                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3794                ],
3795            }
3796        );
3797
3798        // Test Authorize Subcommand w/ sign-only
3799        let blockhash = Hash::default();
3800        let blockhash_string = format!("{blockhash}");
3801        let test_authorize = test_commands.clone().get_matches_from(vec![
3802            "test",
3803            "stake-authorize",
3804            &stake_account_string,
3805            "--new-stake-authority",
3806            &stake_account_string,
3807            "--blockhash",
3808            &blockhash_string,
3809            "--sign-only",
3810        ]);
3811        assert_eq!(
3812            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3813            CliCommandInfo {
3814                command: CliCommand::StakeAuthorize {
3815                    stake_account_pubkey,
3816                    new_authorizations: vec![StakeAuthorizationIndexed {
3817                        authorization_type: StakeAuthorize::Staker,
3818                        new_authority_pubkey: stake_account_pubkey,
3819                        authority: 0,
3820                        new_authority_signer: None,
3821                    }],
3822                    sign_only: true,
3823                    dump_transaction_message: false,
3824                    blockhash_query: BlockhashQuery::Static(blockhash),
3825                    nonce_account: None,
3826                    nonce_authority: 0,
3827                    memo: None,
3828                    fee_payer: 0,
3829                    custodian: None,
3830                    no_wait: false,
3831                    compute_unit_price: None,
3832                },
3833                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3834            }
3835        );
3836        // Test Authorize Subcommand w/ offline feepayer
3837        let keypair = Keypair::new();
3838        let pubkey = keypair.pubkey();
3839        let sig = keypair.sign_message(&[0u8]);
3840        let signer = format!("{}={}", keypair.pubkey(), sig);
3841        let test_authorize = test_commands.clone().get_matches_from(vec![
3842            "test",
3843            "stake-authorize",
3844            &stake_account_string,
3845            "--new-stake-authority",
3846            &stake_account_string,
3847            "--blockhash",
3848            &blockhash_string,
3849            "--signer",
3850            &signer,
3851            "--fee-payer",
3852            &pubkey.to_string(),
3853        ]);
3854        assert_eq!(
3855            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3856            CliCommandInfo {
3857                command: CliCommand::StakeAuthorize {
3858                    stake_account_pubkey,
3859                    new_authorizations: vec![StakeAuthorizationIndexed {
3860                        authorization_type: StakeAuthorize::Staker,
3861                        new_authority_pubkey: stake_account_pubkey,
3862                        authority: 0,
3863                        new_authority_signer: None,
3864                    }],
3865                    sign_only: false,
3866                    dump_transaction_message: false,
3867                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3868                    nonce_account: None,
3869                    nonce_authority: 0,
3870                    memo: None,
3871                    fee_payer: 1,
3872                    custodian: None,
3873                    no_wait: false,
3874                    compute_unit_price: None,
3875                },
3876                signers: vec![
3877                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3878                    Box::new(Presigner::new(&pubkey, &sig))
3879                ],
3880            }
3881        );
3882        // Test Authorize Subcommand w/ offline fee payer and nonce authority
3883        let keypair2 = Keypair::new();
3884        let pubkey2 = keypair2.pubkey();
3885        let sig2 = keypair.sign_message(&[0u8]);
3886        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
3887        let nonce_account = Pubkey::from([1u8; 32]);
3888        let test_authorize = test_commands.clone().get_matches_from(vec![
3889            "test",
3890            "stake-authorize",
3891            &stake_account_string,
3892            "--new-stake-authority",
3893            &stake_account_string,
3894            "--blockhash",
3895            &blockhash_string,
3896            "--signer",
3897            &signer,
3898            "--signer",
3899            &signer2,
3900            "--fee-payer",
3901            &pubkey.to_string(),
3902            "--nonce",
3903            &nonce_account.to_string(),
3904            "--nonce-authority",
3905            &pubkey2.to_string(),
3906        ]);
3907        assert_eq!(
3908            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3909            CliCommandInfo {
3910                command: CliCommand::StakeAuthorize {
3911                    stake_account_pubkey,
3912                    new_authorizations: vec![StakeAuthorizationIndexed {
3913                        authorization_type: StakeAuthorize::Staker,
3914                        new_authority_pubkey: stake_account_pubkey,
3915                        authority: 0,
3916                        new_authority_signer: None,
3917                    }],
3918                    sign_only: false,
3919                    dump_transaction_message: false,
3920                    blockhash_query: BlockhashQuery::Validated(
3921                        Source::NonceAccount(nonce_account),
3922                        blockhash
3923                    ),
3924                    nonce_account: Some(nonce_account),
3925                    nonce_authority: 2,
3926                    memo: None,
3927                    fee_payer: 1,
3928                    custodian: None,
3929                    no_wait: false,
3930                    compute_unit_price: None,
3931                },
3932                signers: vec![
3933                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3934                    Box::new(Presigner::new(&pubkey, &sig)),
3935                    Box::new(Presigner::new(&pubkey2, &sig2)),
3936                ],
3937            }
3938        );
3939        // Test Authorize Subcommand w/ blockhash
3940        let test_authorize = test_commands.clone().get_matches_from(vec![
3941            "test",
3942            "stake-authorize",
3943            &stake_account_string,
3944            "--new-stake-authority",
3945            &stake_account_string,
3946            "--blockhash",
3947            &blockhash_string,
3948        ]);
3949        assert_eq!(
3950            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3951            CliCommandInfo {
3952                command: CliCommand::StakeAuthorize {
3953                    stake_account_pubkey,
3954                    new_authorizations: vec![StakeAuthorizationIndexed {
3955                        authorization_type: StakeAuthorize::Staker,
3956                        new_authority_pubkey: stake_account_pubkey,
3957                        authority: 0,
3958                        new_authority_signer: None,
3959                    }],
3960                    sign_only: false,
3961                    dump_transaction_message: false,
3962                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3963                    nonce_account: None,
3964                    nonce_authority: 0,
3965                    memo: None,
3966                    fee_payer: 0,
3967                    custodian: None,
3968                    no_wait: false,
3969                    compute_unit_price: None,
3970                },
3971                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3972            }
3973        );
3974        // Test Authorize Subcommand w/ nonce
3975        let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
3976        let nonce_authority_keypair = Keypair::new();
3977        write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
3978        let nonce_account_pubkey = nonce_authority_keypair.pubkey();
3979        let nonce_account_string = nonce_account_pubkey.to_string();
3980        let test_authorize = test_commands.clone().get_matches_from(vec![
3981            "test",
3982            "stake-authorize",
3983            &stake_account_string,
3984            "--new-stake-authority",
3985            &stake_account_string,
3986            "--blockhash",
3987            &blockhash_string,
3988            "--nonce",
3989            &nonce_account_string,
3990            "--nonce-authority",
3991            &nonce_keypair_file,
3992        ]);
3993        assert_eq!(
3994            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3995            CliCommandInfo {
3996                command: CliCommand::StakeAuthorize {
3997                    stake_account_pubkey,
3998                    new_authorizations: vec![StakeAuthorizationIndexed {
3999                        authorization_type: StakeAuthorize::Staker,
4000                        new_authority_pubkey: stake_account_pubkey,
4001                        authority: 0,
4002                        new_authority_signer: None,
4003                    }],
4004                    sign_only: false,
4005                    dump_transaction_message: false,
4006                    blockhash_query: BlockhashQuery::Validated(
4007                        Source::NonceAccount(nonce_account_pubkey),
4008                        blockhash
4009                    ),
4010                    nonce_account: Some(nonce_account_pubkey),
4011                    nonce_authority: 1,
4012                    memo: None,
4013                    fee_payer: 0,
4014                    custodian: None,
4015                    no_wait: false,
4016                    compute_unit_price: None,
4017                },
4018                signers: vec![
4019                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4020                    Box::new(nonce_authority_keypair)
4021                ],
4022            }
4023        );
4024        // Test Authorize Subcommand w/ fee-payer
4025        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4026        let fee_payer_keypair = Keypair::new();
4027        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4028        let fee_payer_pubkey = fee_payer_keypair.pubkey();
4029        let fee_payer_string = fee_payer_pubkey.to_string();
4030        let test_authorize = test_commands.clone().get_matches_from(vec![
4031            "test",
4032            "stake-authorize",
4033            &stake_account_string,
4034            "--new-stake-authority",
4035            &stake_account_string,
4036            "--fee-payer",
4037            &fee_payer_keypair_file,
4038        ]);
4039        assert_eq!(
4040            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4041            CliCommandInfo {
4042                command: CliCommand::StakeAuthorize {
4043                    stake_account_pubkey,
4044                    new_authorizations: vec![StakeAuthorizationIndexed {
4045                        authorization_type: StakeAuthorize::Staker,
4046                        new_authority_pubkey: stake_account_pubkey,
4047                        authority: 0,
4048                        new_authority_signer: None,
4049                    }],
4050                    sign_only: false,
4051                    dump_transaction_message: false,
4052                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4053                    nonce_account: None,
4054                    nonce_authority: 0,
4055                    memo: None,
4056                    fee_payer: 1,
4057                    custodian: None,
4058                    no_wait: false,
4059                    compute_unit_price: None,
4060                },
4061                signers: vec![
4062                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4063                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap()),
4064                ],
4065            }
4066        );
4067        // Test Authorize Subcommand w/ absentee fee-payer
4068        let sig = fee_payer_keypair.sign_message(&[0u8]);
4069        let signer = format!("{fee_payer_string}={sig}");
4070        let test_authorize = test_commands.clone().get_matches_from(vec![
4071            "test",
4072            "stake-authorize",
4073            &stake_account_string,
4074            "--new-stake-authority",
4075            &stake_account_string,
4076            "--fee-payer",
4077            &fee_payer_string,
4078            "--blockhash",
4079            &blockhash_string,
4080            "--signer",
4081            &signer,
4082        ]);
4083        assert_eq!(
4084            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4085            CliCommandInfo {
4086                command: CliCommand::StakeAuthorize {
4087                    stake_account_pubkey,
4088                    new_authorizations: vec![StakeAuthorizationIndexed {
4089                        authorization_type: StakeAuthorize::Staker,
4090                        new_authority_pubkey: stake_account_pubkey,
4091                        authority: 0,
4092                        new_authority_signer: None,
4093                    }],
4094                    sign_only: false,
4095                    dump_transaction_message: false,
4096                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4097                    nonce_account: None,
4098                    nonce_authority: 0,
4099                    memo: None,
4100                    fee_payer: 1,
4101                    custodian: None,
4102                    no_wait: false,
4103                    compute_unit_price: None,
4104                },
4105                signers: vec![
4106                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4107                    Box::new(Presigner::new(&fee_payer_pubkey, &sig))
4108                ],
4109            }
4110        );
4111
4112        // Test CreateStakeAccount SubCommand
4113        let custodian = solana_pubkey::new_rand();
4114        let custodian_string = format!("{custodian}");
4115        let authorized = solana_pubkey::new_rand();
4116        let authorized_string = format!("{authorized}");
4117        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4118            "test",
4119            "create-stake-account",
4120            &keypair_file,
4121            "50",
4122            "--stake-authority",
4123            &authorized_string,
4124            "--withdraw-authority",
4125            &authorized_string,
4126            "--custodian",
4127            &custodian_string,
4128            "--lockup-epoch",
4129            "43",
4130        ]);
4131        assert_eq!(
4132            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4133            CliCommandInfo {
4134                command: CliCommand::CreateStakeAccount {
4135                    stake_account: 1,
4136                    seed: None,
4137                    staker: Some(authorized),
4138                    withdrawer: Some(authorized),
4139                    withdrawer_signer: None,
4140                    lockup: Lockup {
4141                        epoch: 43,
4142                        unix_timestamp: 0,
4143                        custodian,
4144                    },
4145                    amount: SpendAmount::Some(50_000_000_000),
4146                    sign_only: false,
4147                    dump_transaction_message: false,
4148                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4149                    nonce_account: None,
4150                    nonce_authority: 0,
4151                    memo: None,
4152                    fee_payer: 0,
4153                    from: 0,
4154                    compute_unit_price: None,
4155                },
4156                signers: vec![
4157                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4158                    Box::new(stake_account_keypair)
4159                ],
4160            }
4161        );
4162
4163        let (keypair_file, mut tmp_file) = make_tmp_file();
4164        let stake_account_keypair = Keypair::new();
4165        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4166        let stake_account_pubkey = stake_account_keypair.pubkey();
4167        let stake_account_string = stake_account_pubkey.to_string();
4168
4169        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4170            "test",
4171            "create-stake-account",
4172            &keypair_file,
4173            "50",
4174        ]);
4175
4176        assert_eq!(
4177            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4178            CliCommandInfo {
4179                command: CliCommand::CreateStakeAccount {
4180                    stake_account: 1,
4181                    seed: None,
4182                    staker: None,
4183                    withdrawer: None,
4184                    withdrawer_signer: None,
4185                    lockup: Lockup::default(),
4186                    amount: SpendAmount::Some(50_000_000_000),
4187                    sign_only: false,
4188                    dump_transaction_message: false,
4189                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4190                    nonce_account: None,
4191                    nonce_authority: 0,
4192                    memo: None,
4193                    fee_payer: 0,
4194                    from: 0,
4195                    compute_unit_price: None,
4196                },
4197                signers: vec![
4198                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4199                    Box::new(read_keypair_file(&keypair_file).unwrap())
4200                ],
4201            }
4202        );
4203        let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
4204        let withdrawer_keypair = Keypair::new();
4205        write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
4206        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4207            "test",
4208            "create-stake-account-checked",
4209            &keypair_file,
4210            "50",
4211            "--stake-authority",
4212            &authorized_string,
4213            "--withdraw-authority",
4214            &withdrawer_keypair_file,
4215        ]);
4216        assert_eq!(
4217            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4218            CliCommandInfo {
4219                command: CliCommand::CreateStakeAccount {
4220                    stake_account: 1,
4221                    seed: None,
4222                    staker: Some(authorized),
4223                    withdrawer: Some(withdrawer_keypair.pubkey()),
4224                    withdrawer_signer: Some(2),
4225                    lockup: Lockup::default(),
4226                    amount: SpendAmount::Some(50_000_000_000),
4227                    sign_only: false,
4228                    dump_transaction_message: false,
4229                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4230                    nonce_account: None,
4231                    nonce_authority: 0,
4232                    memo: None,
4233                    fee_payer: 0,
4234                    from: 0,
4235                    compute_unit_price: None,
4236                },
4237                signers: vec![
4238                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4239                    Box::new(stake_account_keypair),
4240                    Box::new(withdrawer_keypair),
4241                ],
4242            }
4243        );
4244
4245        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4246            "test",
4247            "create-stake-account-checked",
4248            &keypair_file,
4249            "50",
4250            "--stake-authority",
4251            &authorized_string,
4252            "--withdraw-authority",
4253            &authorized_string,
4254        ]);
4255        assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());
4256
4257        // CreateStakeAccount offline and nonce
4258        let nonce_account = Pubkey::from([1u8; 32]);
4259        let nonce_account_string = nonce_account.to_string();
4260        let offline = keypair_from_seed(&[2u8; 32]).unwrap();
4261        let offline_pubkey = offline.pubkey();
4262        let offline_string = offline_pubkey.to_string();
4263        let offline_sig = offline.sign_message(&[3u8]);
4264        let offline_signer = format!("{offline_pubkey}={offline_sig}");
4265        let nonce_hash = Hash::new_from_array([4u8; 32]);
4266        let nonce_hash_string = nonce_hash.to_string();
4267        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4268            "test",
4269            "create-stake-account",
4270            &keypair_file,
4271            "50",
4272            "--blockhash",
4273            &nonce_hash_string,
4274            "--nonce",
4275            &nonce_account_string,
4276            "--nonce-authority",
4277            &offline_string,
4278            "--fee-payer",
4279            &offline_string,
4280            "--from",
4281            &offline_string,
4282            "--signer",
4283            &offline_signer,
4284        ]);
4285
4286        assert_eq!(
4287            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4288            CliCommandInfo {
4289                command: CliCommand::CreateStakeAccount {
4290                    stake_account: 1,
4291                    seed: None,
4292                    staker: None,
4293                    withdrawer: None,
4294                    withdrawer_signer: None,
4295                    lockup: Lockup::default(),
4296                    amount: SpendAmount::Some(50_000_000_000),
4297                    sign_only: false,
4298                    dump_transaction_message: false,
4299                    blockhash_query: BlockhashQuery::Validated(
4300                        Source::NonceAccount(nonce_account),
4301                        nonce_hash
4302                    ),
4303                    nonce_account: Some(nonce_account),
4304                    nonce_authority: 0,
4305                    memo: None,
4306                    fee_payer: 0,
4307                    from: 0,
4308                    compute_unit_price: None,
4309                },
4310                signers: vec![
4311                    Box::new(Presigner::new(&offline_pubkey, &offline_sig)),
4312                    Box::new(read_keypair_file(&keypair_file).unwrap())
4313                ],
4314            }
4315        );
4316
4317        // Test DelegateStake Subcommand
4318        let vote_account_pubkey = solana_pubkey::new_rand();
4319        let vote_account_string = vote_account_pubkey.to_string();
4320        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4321            "test",
4322            "delegate-stake",
4323            &stake_account_string,
4324            &vote_account_string,
4325        ]);
4326        assert_eq!(
4327            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4328            CliCommandInfo {
4329                command: CliCommand::DelegateStake {
4330                    stake_account_pubkey,
4331                    vote_account_pubkey,
4332                    stake_authority: 0,
4333                    force: false,
4334                    sign_only: false,
4335                    dump_transaction_message: false,
4336                    blockhash_query: BlockhashQuery::default(),
4337                    nonce_account: None,
4338                    nonce_authority: 0,
4339                    memo: None,
4340                    fee_payer: 0,
4341                    compute_unit_price: None,
4342                },
4343                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4344            }
4345        );
4346
4347        // Test DelegateStake Subcommand w/ authority
4348        let vote_account_pubkey = solana_pubkey::new_rand();
4349        let vote_account_string = vote_account_pubkey.to_string();
4350        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4351            "test",
4352            "delegate-stake",
4353            &stake_account_string,
4354            &vote_account_string,
4355            "--stake-authority",
4356            &stake_authority_keypair_file,
4357        ]);
4358        assert_eq!(
4359            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4360            CliCommandInfo {
4361                command: CliCommand::DelegateStake {
4362                    stake_account_pubkey,
4363                    vote_account_pubkey,
4364                    stake_authority: 1,
4365                    force: false,
4366                    sign_only: false,
4367                    dump_transaction_message: false,
4368                    blockhash_query: BlockhashQuery::default(),
4369                    nonce_account: None,
4370                    nonce_authority: 0,
4371                    memo: None,
4372                    fee_payer: 0,
4373                    compute_unit_price: None,
4374                },
4375                signers: vec![
4376                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4377                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4378                ],
4379            }
4380        );
4381
4382        // Test DelegateStake Subcommand w/ force
4383        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4384            "test",
4385            "delegate-stake",
4386            "--force",
4387            &stake_account_string,
4388            &vote_account_string,
4389        ]);
4390        assert_eq!(
4391            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4392            CliCommandInfo {
4393                command: CliCommand::DelegateStake {
4394                    stake_account_pubkey,
4395                    vote_account_pubkey,
4396                    stake_authority: 0,
4397                    force: true,
4398                    sign_only: false,
4399                    dump_transaction_message: false,
4400                    blockhash_query: BlockhashQuery::default(),
4401                    nonce_account: None,
4402                    nonce_authority: 0,
4403                    memo: None,
4404                    fee_payer: 0,
4405                    compute_unit_price: None,
4406                },
4407                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4408            }
4409        );
4410
4411        // Test Delegate Subcommand w/ Blockhash
4412        let blockhash = Hash::default();
4413        let blockhash_string = format!("{blockhash}");
4414        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4415            "test",
4416            "delegate-stake",
4417            &stake_account_string,
4418            &vote_account_string,
4419            "--blockhash",
4420            &blockhash_string,
4421        ]);
4422        assert_eq!(
4423            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4424            CliCommandInfo {
4425                command: CliCommand::DelegateStake {
4426                    stake_account_pubkey,
4427                    vote_account_pubkey,
4428                    stake_authority: 0,
4429                    force: false,
4430                    sign_only: false,
4431                    dump_transaction_message: false,
4432                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4433                    nonce_account: None,
4434                    nonce_authority: 0,
4435                    memo: None,
4436                    fee_payer: 0,
4437                    compute_unit_price: None,
4438                },
4439                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4440            }
4441        );
4442
4443        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4444            "test",
4445            "delegate-stake",
4446            &stake_account_string,
4447            &vote_account_string,
4448            "--blockhash",
4449            &blockhash_string,
4450            "--sign-only",
4451        ]);
4452        assert_eq!(
4453            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4454            CliCommandInfo {
4455                command: CliCommand::DelegateStake {
4456                    stake_account_pubkey,
4457                    vote_account_pubkey,
4458                    stake_authority: 0,
4459                    force: false,
4460                    sign_only: true,
4461                    dump_transaction_message: false,
4462                    blockhash_query: BlockhashQuery::Static(blockhash),
4463                    nonce_account: None,
4464                    nonce_authority: 0,
4465                    memo: None,
4466                    fee_payer: 0,
4467                    compute_unit_price: None,
4468                },
4469                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4470            }
4471        );
4472
4473        // Test Delegate Subcommand w/ absent fee payer
4474        let key1 = solana_pubkey::new_rand();
4475        let sig1 = Keypair::new().sign_message(&[0u8]);
4476        let signer1 = format!("{key1}={sig1}");
4477        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4478            "test",
4479            "delegate-stake",
4480            &stake_account_string,
4481            &vote_account_string,
4482            "--blockhash",
4483            &blockhash_string,
4484            "--signer",
4485            &signer1,
4486            "--fee-payer",
4487            &key1.to_string(),
4488        ]);
4489        assert_eq!(
4490            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4491            CliCommandInfo {
4492                command: CliCommand::DelegateStake {
4493                    stake_account_pubkey,
4494                    vote_account_pubkey,
4495                    stake_authority: 0,
4496                    force: false,
4497                    sign_only: false,
4498                    dump_transaction_message: false,
4499                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4500                    nonce_account: None,
4501                    nonce_authority: 0,
4502                    memo: None,
4503                    fee_payer: 1,
4504                    compute_unit_price: None,
4505                },
4506                signers: vec![
4507                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4508                    Box::new(Presigner::new(&key1, &sig1))
4509                ],
4510            }
4511        );
4512
4513        // Test Delegate Subcommand w/ absent fee payer and absent nonce authority
4514        let key2 = solana_pubkey::new_rand();
4515        let sig2 = Keypair::new().sign_message(&[0u8]);
4516        let signer2 = format!("{key2}={sig2}");
4517        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4518            "test",
4519            "delegate-stake",
4520            &stake_account_string,
4521            &vote_account_string,
4522            "--blockhash",
4523            &blockhash_string,
4524            "--signer",
4525            &signer1,
4526            "--signer",
4527            &signer2,
4528            "--fee-payer",
4529            &key1.to_string(),
4530            "--nonce",
4531            &nonce_account.to_string(),
4532            "--nonce-authority",
4533            &key2.to_string(),
4534        ]);
4535        assert_eq!(
4536            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4537            CliCommandInfo {
4538                command: CliCommand::DelegateStake {
4539                    stake_account_pubkey,
4540                    vote_account_pubkey,
4541                    stake_authority: 0,
4542                    force: false,
4543                    sign_only: false,
4544                    dump_transaction_message: false,
4545                    blockhash_query: BlockhashQuery::Validated(
4546                        Source::NonceAccount(nonce_account),
4547                        blockhash
4548                    ),
4549                    nonce_account: Some(nonce_account),
4550                    nonce_authority: 2,
4551                    memo: None,
4552                    fee_payer: 1,
4553                    compute_unit_price: None,
4554                },
4555                signers: vec![
4556                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4557                    Box::new(Presigner::new(&key1, &sig1)),
4558                    Box::new(Presigner::new(&key2, &sig2)),
4559                ],
4560            }
4561        );
4562
4563        // Test Delegate Subcommand w/ present fee-payer
4564        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4565        let fee_payer_keypair = Keypair::new();
4566        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4567        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4568            "test",
4569            "delegate-stake",
4570            &stake_account_string,
4571            &vote_account_string,
4572            "--fee-payer",
4573            &fee_payer_keypair_file,
4574        ]);
4575        assert_eq!(
4576            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4577            CliCommandInfo {
4578                command: CliCommand::DelegateStake {
4579                    stake_account_pubkey,
4580                    vote_account_pubkey,
4581                    stake_authority: 0,
4582                    force: false,
4583                    sign_only: false,
4584                    dump_transaction_message: false,
4585                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4586                    nonce_account: None,
4587                    nonce_authority: 0,
4588                    memo: None,
4589                    fee_payer: 1,
4590                    compute_unit_price: None,
4591                },
4592                signers: vec![
4593                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4594                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
4595                ],
4596            }
4597        );
4598
4599        // Test WithdrawStake Subcommand
4600        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4601            "test",
4602            "withdraw-stake",
4603            &stake_account_string,
4604            &stake_account_string,
4605            "42",
4606        ]);
4607
4608        assert_eq!(
4609            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4610            CliCommandInfo {
4611                command: CliCommand::WithdrawStake {
4612                    stake_account_pubkey,
4613                    destination_account_pubkey: stake_account_pubkey,
4614                    amount: SpendAmount::Some(42_000_000_000),
4615                    withdraw_authority: 0,
4616                    custodian: None,
4617                    sign_only: false,
4618                    dump_transaction_message: false,
4619                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4620                    nonce_account: None,
4621                    nonce_authority: 0,
4622                    memo: None,
4623                    seed: None,
4624                    fee_payer: 0,
4625                    compute_unit_price: None,
4626                },
4627                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4628            }
4629        );
4630
4631        // Test WithdrawStake Subcommand w/ AVAILABLE amount
4632        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4633            "test",
4634            "withdraw-stake",
4635            &stake_account_string,
4636            &stake_account_string,
4637            "AVAILABLE",
4638        ]);
4639
4640        assert_eq!(
4641            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4642            CliCommandInfo {
4643                command: CliCommand::WithdrawStake {
4644                    stake_account_pubkey,
4645                    destination_account_pubkey: stake_account_pubkey,
4646                    amount: SpendAmount::Available,
4647                    withdraw_authority: 0,
4648                    custodian: None,
4649                    sign_only: false,
4650                    dump_transaction_message: false,
4651                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4652                    nonce_account: None,
4653                    nonce_authority: 0,
4654                    memo: None,
4655                    seed: None,
4656                    fee_payer: 0,
4657                    compute_unit_price: None,
4658                },
4659                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4660            }
4661        );
4662
4663        // Test WithdrawStake Subcommand w/ ComputeUnitPrice
4664        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4665            "test",
4666            "withdraw-stake",
4667            &stake_account_string,
4668            &stake_account_string,
4669            "42",
4670            "--with-compute-unit-price",
4671            "99",
4672        ]);
4673
4674        assert_eq!(
4675            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4676            CliCommandInfo {
4677                command: CliCommand::WithdrawStake {
4678                    stake_account_pubkey,
4679                    destination_account_pubkey: stake_account_pubkey,
4680                    amount: SpendAmount::Some(42_000_000_000),
4681                    withdraw_authority: 0,
4682                    custodian: None,
4683                    sign_only: false,
4684                    dump_transaction_message: false,
4685                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4686                    nonce_account: None,
4687                    nonce_authority: 0,
4688                    memo: None,
4689                    seed: None,
4690                    fee_payer: 0,
4691                    compute_unit_price: Some(99),
4692                },
4693                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4694            }
4695        );
4696
4697        // Test WithdrawStake Subcommand w/ authority
4698        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4699            "test",
4700            "withdraw-stake",
4701            &stake_account_string,
4702            &stake_account_string,
4703            "42",
4704            "--withdraw-authority",
4705            &stake_authority_keypair_file,
4706        ]);
4707
4708        assert_eq!(
4709            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4710            CliCommandInfo {
4711                command: CliCommand::WithdrawStake {
4712                    stake_account_pubkey,
4713                    destination_account_pubkey: stake_account_pubkey,
4714                    amount: SpendAmount::Some(42_000_000_000),
4715                    withdraw_authority: 1,
4716                    custodian: None,
4717                    sign_only: false,
4718                    dump_transaction_message: false,
4719                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4720                    nonce_account: None,
4721                    nonce_authority: 0,
4722                    memo: None,
4723                    seed: None,
4724                    fee_payer: 0,
4725                    compute_unit_price: None,
4726                },
4727                signers: vec![
4728                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4729                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4730                ],
4731            }
4732        );
4733
4734        // Test WithdrawStake Subcommand w/ custodian
4735        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4736            "test",
4737            "withdraw-stake",
4738            &stake_account_string,
4739            &stake_account_string,
4740            "42",
4741            "--custodian",
4742            &custodian_keypair_file,
4743        ]);
4744
4745        assert_eq!(
4746            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4747            CliCommandInfo {
4748                command: CliCommand::WithdrawStake {
4749                    stake_account_pubkey,
4750                    destination_account_pubkey: stake_account_pubkey,
4751                    amount: SpendAmount::Some(42_000_000_000),
4752                    withdraw_authority: 0,
4753                    custodian: Some(1),
4754                    sign_only: false,
4755                    dump_transaction_message: false,
4756                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4757                    nonce_account: None,
4758                    nonce_authority: 0,
4759                    memo: None,
4760                    seed: None,
4761                    fee_payer: 0,
4762                    compute_unit_price: None,
4763                },
4764                signers: vec![
4765                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4766                    Box::new(read_keypair_file(&custodian_keypair_file).unwrap())
4767                ],
4768            }
4769        );
4770
4771        // Test WithdrawStake Subcommand w/ authority and offline nonce
4772        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4773            "test",
4774            "withdraw-stake",
4775            &stake_account_string,
4776            &stake_account_string,
4777            "42",
4778            "--withdraw-authority",
4779            &stake_authority_keypair_file,
4780            "--blockhash",
4781            &nonce_hash_string,
4782            "--nonce",
4783            &nonce_account_string,
4784            "--nonce-authority",
4785            &offline_string,
4786            "--fee-payer",
4787            &offline_string,
4788            "--signer",
4789            &offline_signer,
4790        ]);
4791
4792        assert_eq!(
4793            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4794            CliCommandInfo {
4795                command: CliCommand::WithdrawStake {
4796                    stake_account_pubkey,
4797                    destination_account_pubkey: stake_account_pubkey,
4798                    amount: SpendAmount::Some(42_000_000_000),
4799                    withdraw_authority: 0,
4800                    custodian: None,
4801                    sign_only: false,
4802                    dump_transaction_message: false,
4803                    blockhash_query: BlockhashQuery::Validated(
4804                        Source::NonceAccount(nonce_account),
4805                        nonce_hash
4806                    ),
4807                    nonce_account: Some(nonce_account),
4808                    nonce_authority: 1,
4809                    memo: None,
4810                    seed: None,
4811                    fee_payer: 1,
4812                    compute_unit_price: None,
4813                },
4814                signers: vec![
4815                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
4816                    Box::new(Presigner::new(&offline_pubkey, &offline_sig))
4817                ],
4818            }
4819        );
4820
4821        // Test DeactivateStake Subcommand
4822        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4823            "test",
4824            "deactivate-stake",
4825            &stake_account_string,
4826        ]);
4827        assert_eq!(
4828            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4829            CliCommandInfo {
4830                command: CliCommand::DeactivateStake {
4831                    stake_account_pubkey,
4832                    stake_authority: 0,
4833                    sign_only: false,
4834                    deactivate_delinquent: false,
4835                    dump_transaction_message: false,
4836                    blockhash_query: BlockhashQuery::default(),
4837                    nonce_account: None,
4838                    nonce_authority: 0,
4839                    memo: None,
4840                    seed: None,
4841                    fee_payer: 0,
4842                    compute_unit_price: None,
4843                },
4844                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4845            }
4846        );
4847
4848        // Test DeactivateStake Subcommand with delinquent flag
4849        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4850            "test",
4851            "deactivate-stake",
4852            &stake_account_string,
4853            "--delinquent",
4854        ]);
4855        assert_eq!(
4856            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4857            CliCommandInfo {
4858                command: CliCommand::DeactivateStake {
4859                    stake_account_pubkey,
4860                    stake_authority: 0,
4861                    sign_only: false,
4862                    deactivate_delinquent: true,
4863                    dump_transaction_message: false,
4864                    blockhash_query: BlockhashQuery::default(),
4865                    nonce_account: None,
4866                    nonce_authority: 0,
4867                    memo: None,
4868                    seed: None,
4869                    fee_payer: 0,
4870                    compute_unit_price: None,
4871                },
4872                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4873            }
4874        );
4875
4876        // Test DeactivateStake Subcommand w/ authority
4877        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4878            "test",
4879            "deactivate-stake",
4880            &stake_account_string,
4881            "--stake-authority",
4882            &stake_authority_keypair_file,
4883        ]);
4884        assert_eq!(
4885            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4886            CliCommandInfo {
4887                command: CliCommand::DeactivateStake {
4888                    stake_account_pubkey,
4889                    stake_authority: 1,
4890                    sign_only: false,
4891                    deactivate_delinquent: false,
4892                    dump_transaction_message: false,
4893                    blockhash_query: BlockhashQuery::default(),
4894                    nonce_account: None,
4895                    nonce_authority: 0,
4896                    memo: None,
4897                    seed: None,
4898                    fee_payer: 0,
4899                    compute_unit_price: None,
4900                },
4901                signers: vec![
4902                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4903                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4904                ],
4905            }
4906        );
4907
4908        // Test Deactivate Subcommand w/ Blockhash
4909        let blockhash = Hash::default();
4910        let blockhash_string = format!("{blockhash}");
4911        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4912            "test",
4913            "deactivate-stake",
4914            &stake_account_string,
4915            "--blockhash",
4916            &blockhash_string,
4917        ]);
4918        assert_eq!(
4919            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4920            CliCommandInfo {
4921                command: CliCommand::DeactivateStake {
4922                    stake_account_pubkey,
4923                    stake_authority: 0,
4924                    sign_only: false,
4925                    deactivate_delinquent: false,
4926                    dump_transaction_message: false,
4927                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4928                    nonce_account: None,
4929                    nonce_authority: 0,
4930                    memo: None,
4931                    seed: None,
4932                    fee_payer: 0,
4933                    compute_unit_price: None,
4934                },
4935                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4936            }
4937        );
4938
4939        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4940            "test",
4941            "deactivate-stake",
4942            &stake_account_string,
4943            "--blockhash",
4944            &blockhash_string,
4945            "--sign-only",
4946        ]);
4947        assert_eq!(
4948            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4949            CliCommandInfo {
4950                command: CliCommand::DeactivateStake {
4951                    stake_account_pubkey,
4952                    stake_authority: 0,
4953                    sign_only: true,
4954                    deactivate_delinquent: false,
4955                    dump_transaction_message: false,
4956                    blockhash_query: BlockhashQuery::Static(blockhash),
4957                    nonce_account: None,
4958                    nonce_authority: 0,
4959                    memo: None,
4960                    seed: None,
4961                    fee_payer: 0,
4962                    compute_unit_price: None,
4963                },
4964                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4965            }
4966        );
4967
4968        // Test Deactivate Subcommand w/ absent fee payer
4969        let key1 = solana_pubkey::new_rand();
4970        let sig1 = Keypair::new().sign_message(&[0u8]);
4971        let signer1 = format!("{key1}={sig1}");
4972        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4973            "test",
4974            "deactivate-stake",
4975            &stake_account_string,
4976            "--blockhash",
4977            &blockhash_string,
4978            "--signer",
4979            &signer1,
4980            "--fee-payer",
4981            &key1.to_string(),
4982        ]);
4983        assert_eq!(
4984            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4985            CliCommandInfo {
4986                command: CliCommand::DeactivateStake {
4987                    stake_account_pubkey,
4988                    stake_authority: 0,
4989                    sign_only: false,
4990                    deactivate_delinquent: false,
4991                    dump_transaction_message: false,
4992                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4993                    nonce_account: None,
4994                    nonce_authority: 0,
4995                    memo: None,
4996                    seed: None,
4997                    fee_payer: 1,
4998                    compute_unit_price: None,
4999                },
5000                signers: vec![
5001                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5002                    Box::new(Presigner::new(&key1, &sig1))
5003                ],
5004            }
5005        );
5006
5007        // Test Deactivate Subcommand w/ absent fee payer and nonce authority
5008        let key2 = solana_pubkey::new_rand();
5009        let sig2 = Keypair::new().sign_message(&[0u8]);
5010        let signer2 = format!("{key2}={sig2}");
5011        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5012            "test",
5013            "deactivate-stake",
5014            &stake_account_string,
5015            "--blockhash",
5016            &blockhash_string,
5017            "--signer",
5018            &signer1,
5019            "--signer",
5020            &signer2,
5021            "--fee-payer",
5022            &key1.to_string(),
5023            "--nonce",
5024            &nonce_account.to_string(),
5025            "--nonce-authority",
5026            &key2.to_string(),
5027        ]);
5028        assert_eq!(
5029            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5030            CliCommandInfo {
5031                command: CliCommand::DeactivateStake {
5032                    stake_account_pubkey,
5033                    stake_authority: 0,
5034                    sign_only: false,
5035                    deactivate_delinquent: false,
5036                    dump_transaction_message: false,
5037                    blockhash_query: BlockhashQuery::Validated(
5038                        Source::NonceAccount(nonce_account),
5039                        blockhash
5040                    ),
5041                    nonce_account: Some(nonce_account),
5042                    nonce_authority: 2,
5043                    memo: None,
5044                    seed: None,
5045                    fee_payer: 1,
5046                    compute_unit_price: None,
5047                },
5048                signers: vec![
5049                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5050                    Box::new(Presigner::new(&key1, &sig1)),
5051                    Box::new(Presigner::new(&key2, &sig2)),
5052                ],
5053            }
5054        );
5055
5056        // Test Deactivate Subcommand w/ fee-payer
5057        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5058            "test",
5059            "deactivate-stake",
5060            &stake_account_string,
5061            "--fee-payer",
5062            &fee_payer_keypair_file,
5063        ]);
5064        assert_eq!(
5065            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5066            CliCommandInfo {
5067                command: CliCommand::DeactivateStake {
5068                    stake_account_pubkey,
5069                    stake_authority: 0,
5070                    sign_only: false,
5071                    deactivate_delinquent: false,
5072                    dump_transaction_message: false,
5073                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
5074                    nonce_account: None,
5075                    nonce_authority: 0,
5076                    memo: None,
5077                    seed: None,
5078                    fee_payer: 1,
5079                    compute_unit_price: None,
5080                },
5081                signers: vec![
5082                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5083                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
5084                ],
5085            }
5086        );
5087
5088        // Test SplitStake SubCommand
5089        let (keypair_file, mut tmp_file) = make_tmp_file();
5090        let stake_account_keypair = Keypair::new();
5091        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5092        let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
5093        let split_stake_account_keypair = Keypair::new();
5094        write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5095
5096        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5097            "test",
5098            "split-stake",
5099            &keypair_file,
5100            &split_stake_account_keypair_file,
5101            "50",
5102        ]);
5103        assert_eq!(
5104            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5105            CliCommandInfo {
5106                command: CliCommand::SplitStake {
5107                    stake_account_pubkey: stake_account_keypair.pubkey(),
5108                    stake_authority: 0,
5109                    sign_only: false,
5110                    dump_transaction_message: false,
5111                    blockhash_query: BlockhashQuery::default(),
5112                    nonce_account: None,
5113                    nonce_authority: 0,
5114                    memo: None,
5115                    split_stake_account: 1,
5116                    seed: None,
5117                    lamports: 50_000_000_000,
5118                    fee_payer: 0,
5119                    compute_unit_price: None,
5120                    rent_exempt_reserve: None,
5121                },
5122                signers: vec![
5123                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5124                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap())
5125                ],
5126            }
5127        );
5128
5129        // Split stake offline nonced submission
5130        let nonce_account = Pubkey::from([1u8; 32]);
5131        let nonce_account_string = nonce_account.to_string();
5132        let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
5133        let nonce_auth_pubkey = nonce_auth.pubkey();
5134        let nonce_auth_string = nonce_auth_pubkey.to_string();
5135        let nonce_sig = nonce_auth.sign_message(&[0u8]);
5136        let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
5137        let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
5138        let stake_auth_pubkey = stake_auth.pubkey();
5139        let stake_auth_string = stake_auth_pubkey.to_string();
5140        let stake_sig = stake_auth.sign_message(&[0u8]);
5141        let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
5142        let nonce_hash = Hash::new_from_array([4u8; 32]);
5143        let nonce_hash_string = nonce_hash.to_string();
5144
5145        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5146            "test",
5147            "split-stake",
5148            &keypair_file,
5149            &split_stake_account_keypair_file,
5150            "50",
5151            "--stake-authority",
5152            &stake_auth_string,
5153            "--blockhash",
5154            &nonce_hash_string,
5155            "--nonce",
5156            &nonce_account_string,
5157            "--nonce-authority",
5158            &nonce_auth_string,
5159            "--fee-payer",
5160            &nonce_auth_string, // Arbitrary choice of fee-payer
5161            "--signer",
5162            &nonce_signer,
5163            "--signer",
5164            &stake_signer,
5165        ]);
5166        assert_eq!(
5167            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5168            CliCommandInfo {
5169                command: CliCommand::SplitStake {
5170                    stake_account_pubkey: stake_account_keypair.pubkey(),
5171                    stake_authority: 0,
5172                    sign_only: false,
5173                    dump_transaction_message: false,
5174                    blockhash_query: BlockhashQuery::Validated(
5175                        Source::NonceAccount(nonce_account),
5176                        nonce_hash
5177                    ),
5178                    nonce_account: Some(nonce_account),
5179                    nonce_authority: 1,
5180                    memo: None,
5181                    split_stake_account: 2,
5182                    seed: None,
5183                    lamports: 50_000_000_000,
5184                    fee_payer: 1,
5185                    compute_unit_price: None,
5186                    rent_exempt_reserve: None,
5187                },
5188                signers: vec![
5189                    Box::new(Presigner::new(&stake_auth_pubkey, &stake_sig)),
5190                    Box::new(Presigner::new(&nonce_auth_pubkey, &nonce_sig)),
5191                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap()),
5192                ],
5193            }
5194        );
5195
5196        // Test MergeStake SubCommand
5197        let (keypair_file, mut tmp_file) = make_tmp_file();
5198        let stake_account_keypair = Keypair::new();
5199        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5200
5201        let source_stake_account_pubkey = solana_pubkey::new_rand();
5202        let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
5203            "test",
5204            "merge-stake",
5205            &keypair_file,
5206            &source_stake_account_pubkey.to_string(),
5207        ]);
5208        assert_eq!(
5209            parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
5210            CliCommandInfo {
5211                command: CliCommand::MergeStake {
5212                    stake_account_pubkey: stake_account_keypair.pubkey(),
5213                    source_stake_account_pubkey,
5214                    stake_authority: 0,
5215                    sign_only: false,
5216                    dump_transaction_message: false,
5217                    blockhash_query: BlockhashQuery::default(),
5218                    nonce_account: None,
5219                    nonce_authority: 0,
5220                    memo: None,
5221                    fee_payer: 0,
5222                    compute_unit_price: None,
5223                },
5224                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
5225            }
5226        );
5227    }
5228}