Skip to main content

solana_cli/
stake.rs

1use {
2    crate::{
3        checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4        cli::{
5            CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6            log_instruction_custom_error,
7        },
8        compute_budget::{
9            ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
10        },
11        feature::get_feature_activation_epoch,
12        memo::WithMemo,
13        nonce::check_nonce_account,
14        spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balances},
15    },
16    clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand, value_t},
17    solana_account::{Account, from_account, state_traits::StateMut},
18    solana_clap_utils::{
19        ArgConstant,
20        compute_budget::{COMPUTE_UNIT_PRICE_ARG, ComputeUnitLimit, compute_unit_price_arg},
21        fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
22        hidden_unless_forced,
23        input_parsers::*,
24        input_validators::*,
25        keypair::{DefaultSigner, SignerIndex},
26        memo::{MEMO_ARG, memo_arg},
27        nonce::*,
28        offline::*,
29    },
30    solana_cli_output::{
31        self, CliBalance, CliEpochReward, CliStakeHistory, CliStakeHistoryEntry, CliStakeState,
32        CliStakeType, OutputFormat, ReturnSignersConfig, display::BuildBalanceMessageConfig,
33        return_signers_with_config,
34    },
35    solana_clock::{Clock, Epoch, SECONDS_PER_DAY, UnixTimestamp},
36    solana_commitment_config::CommitmentConfig,
37    solana_epoch_schedule::EpochSchedule,
38    solana_message::Message,
39    solana_native_token::Sol,
40    solana_pubkey::Pubkey,
41    solana_remote_wallet::remote_wallet::RemoteWalletManager,
42    solana_rpc_client::nonblocking::rpc_client::RpcClient,
43    solana_rpc_client_api::{
44        config::RpcGetVoteAccountsConfig,
45        request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
46        response::{RpcInflationReward, RpcVoteAccountStatus},
47    },
48    solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
49    solana_sdk_ids::{
50        system_program,
51        sysvar::{clock, stake_history},
52    },
53    solana_stake_interface::{
54        self as stake,
55        error::StakeError,
56        instruction::{self as stake_instruction, LockupArgs},
57        stake_history::StakeHistory,
58        state::{
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 stake_account.state() {
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    use_fixed_point_stake_math: bool,
2446) -> StakeActivationStatus {
2447    if use_fixed_point_stake_math {
2448        delegation.stake_activating_and_deactivating_v2(
2449            current_epoch,
2450            stake_history,
2451            new_rate_activation_epoch,
2452        )
2453    } else {
2454        #[allow(deprecated)]
2455        delegation.stake_activating_and_deactivating(
2456            current_epoch,
2457            stake_history,
2458            new_rate_activation_epoch,
2459        )
2460    }
2461}
2462
2463pub fn build_stake_state(
2464    account_balance: u64,
2465    stake_state: &StakeStateV2,
2466    use_lamports_unit: bool,
2467    stake_history: &StakeHistory,
2468    clock: &Clock,
2469    new_rate_activation_epoch: Option<Epoch>,
2470    rent_exempt_reserve: u64,
2471    use_csv: bool,
2472    use_fixed_point_stake_math: bool,
2473) -> CliStakeState {
2474    match stake_state {
2475        StakeStateV2::Stake(
2476            Meta {
2477                #[expect(deprecated)]
2478                    rent_exempt_reserve: _,
2479                authorized,
2480                lockup,
2481            },
2482            stake,
2483            _,
2484        ) => {
2485            let current_epoch = clock.epoch;
2486            let StakeActivationStatus {
2487                effective,
2488                activating,
2489                deactivating,
2490            } = stake_activation_status(
2491                &stake.delegation,
2492                current_epoch,
2493                stake_history,
2494                new_rate_activation_epoch,
2495                use_fixed_point_stake_math,
2496            );
2497            let lockup = if lockup.is_in_force(clock, None) {
2498                Some(lockup.into())
2499            } else {
2500                None
2501            };
2502            CliStakeState {
2503                stake_type: CliStakeType::Stake,
2504                account_balance,
2505                credits_observed: Some(stake.credits_observed),
2506                delegated_stake: Some(stake.delegation.stake),
2507                delegated_vote_account_address: if stake.delegation.voter_pubkey
2508                    != Pubkey::default()
2509                {
2510                    Some(stake.delegation.voter_pubkey.to_string())
2511                } else {
2512                    None
2513                },
2514                activation_epoch: Some(if stake.delegation.activation_epoch < u64::MAX {
2515                    stake.delegation.activation_epoch
2516                } else {
2517                    0
2518                }),
2519                deactivation_epoch: if stake.delegation.deactivation_epoch < u64::MAX {
2520                    Some(stake.delegation.deactivation_epoch)
2521                } else {
2522                    None
2523                },
2524                authorized: Some(authorized.into()),
2525                lockup,
2526                use_lamports_unit,
2527                current_epoch,
2528                rent_exempt_reserve: Some(rent_exempt_reserve),
2529                active_stake: u64_some_if_not_zero(effective),
2530                activating_stake: u64_some_if_not_zero(activating),
2531                deactivating_stake: u64_some_if_not_zero(deactivating),
2532                use_csv,
2533                ..CliStakeState::default()
2534            }
2535        }
2536        StakeStateV2::RewardsPool => CliStakeState {
2537            stake_type: CliStakeType::RewardsPool,
2538            account_balance,
2539            ..CliStakeState::default()
2540        },
2541        StakeStateV2::Uninitialized => CliStakeState {
2542            account_balance,
2543            ..CliStakeState::default()
2544        },
2545        StakeStateV2::Initialized(Meta {
2546            #[expect(deprecated)]
2547                rent_exempt_reserve: _,
2548            authorized,
2549            lockup,
2550        }) => {
2551            let lockup = if lockup.is_in_force(clock, None) {
2552                Some(lockup.into())
2553            } else {
2554                None
2555            };
2556            CliStakeState {
2557                stake_type: CliStakeType::Initialized,
2558                account_balance,
2559                credits_observed: Some(0),
2560                authorized: Some(authorized.into()),
2561                lockup,
2562                use_lamports_unit,
2563                rent_exempt_reserve: Some(rent_exempt_reserve),
2564                ..CliStakeState::default()
2565            }
2566        }
2567    }
2568}
2569
2570async fn get_stake_account_state(
2571    rpc_client: &RpcClient,
2572    stake_account_pubkey: &Pubkey,
2573    commitment_config: CommitmentConfig,
2574) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
2575    let stake_account = rpc_client
2576        .get_account_with_commitment(stake_account_pubkey, commitment_config)
2577        .await?
2578        .value
2579        .ok_or_else(|| {
2580            CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
2581        })?;
2582    if stake_account.owner != stake::program::id() {
2583        return Err(CliError::RpcRequestError(format!(
2584            "{stake_account_pubkey:?} is not a stake account",
2585        ))
2586        .into());
2587    }
2588    stake_account.state().map_err(|err| {
2589        CliError::RpcRequestError(format!(
2590            "Account data could not be deserialized to stake state: {err}"
2591        ))
2592        .into()
2593    })
2594}
2595
2596pub(crate) fn check_current_authority(
2597    permitted_authorities: &[Pubkey],
2598    provided_current_authority: &Pubkey,
2599) -> Result<(), CliError> {
2600    if !permitted_authorities.contains(provided_current_authority) {
2601        Err(CliError::RpcRequestError(format!(
2602            "Invalid authority provided: {provided_current_authority:?}, expected \
2603             {permitted_authorities:?}"
2604        )))
2605    } else {
2606        Ok(())
2607    }
2608}
2609
2610pub async fn get_epoch_boundary_timestamps(
2611    rpc_client: &RpcClient,
2612    reward: &RpcInflationReward,
2613    epoch_schedule: &EpochSchedule,
2614) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
2615    let epoch_end_time = rpc_client.get_block_time(reward.effective_slot).await?;
2616    let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
2617    let epoch_start_time = loop {
2618        if epoch_start_slot >= reward.effective_slot {
2619            return Err("epoch_start_time not found".to_string().into());
2620        }
2621        match rpc_client.get_block_time(epoch_start_slot).await {
2622            Ok(block_time) => {
2623                break block_time;
2624            }
2625            Err(_) => {
2626                // TODO This is wrong.  We should not just increase the slot index if the RPC
2627                // request failed.  It could have failed for a number of reasons, including, for
2628                // example a network failure.
2629                epoch_start_slot = epoch_start_slot
2630                    .checked_add(1)
2631                    .ok_or("Reached last slot that fits into u64")?;
2632            }
2633        }
2634    };
2635    Ok((epoch_start_time, epoch_end_time))
2636}
2637
2638pub fn make_cli_reward(
2639    reward: &RpcInflationReward,
2640    block_time: UnixTimestamp,
2641    epoch_start_time: UnixTimestamp,
2642    epoch_end_time: UnixTimestamp,
2643) -> Option<CliEpochReward> {
2644    let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
2645    if reward.post_balance > reward.amount {
2646        let rate_change =
2647            reward.amount as f64 / (reward.post_balance.saturating_sub(reward.amount)) as f64;
2648
2649        let wallclock_epochs_per_year =
2650            (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
2651        let apr = rate_change * wallclock_epochs_per_year;
2652
2653        Some(CliEpochReward {
2654            epoch: reward.epoch,
2655            effective_slot: reward.effective_slot,
2656            amount: reward.amount,
2657            post_balance: reward.post_balance,
2658            percent_change: rate_change * 100.0,
2659            apr: Some(apr * 100.0),
2660            commission: reward.commission,
2661            block_time,
2662        })
2663    } else {
2664        None
2665    }
2666}
2667
2668pub(crate) async fn fetch_epoch_rewards(
2669    rpc_client: &RpcClient,
2670    address: &Pubkey,
2671    mut num_epochs: usize,
2672    starting_epoch: Option<u64>,
2673) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
2674    let mut all_epoch_rewards = vec![];
2675    let epoch_schedule = rpc_client.get_epoch_schedule().await?;
2676    let mut rewards_epoch = if let Some(epoch) = starting_epoch {
2677        epoch
2678    } else {
2679        rpc_client
2680            .get_epoch_info()
2681            .await?
2682            .epoch
2683            .saturating_sub(num_epochs as u64)
2684    };
2685
2686    while num_epochs > 0 {
2687        if let Ok(rewards) = rpc_client
2688            .get_inflation_reward(&[*address], Some(rewards_epoch))
2689            .await
2690        {
2691            if let Some(reward) = &rewards[0] {
2692                let (epoch_start_time, epoch_end_time) =
2693                    get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule).await?;
2694                let block_time = rpc_client.get_block_time(reward.effective_slot).await?;
2695                if let Some(cli_reward) =
2696                    make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
2697                {
2698                    all_epoch_rewards.push(cli_reward);
2699                }
2700            }
2701        } else {
2702            eprintln!("Rewards not available for epoch {rewards_epoch}");
2703        }
2704        num_epochs = num_epochs.saturating_sub(1);
2705        rewards_epoch = rewards_epoch.saturating_add(1);
2706    }
2707
2708    Ok(all_epoch_rewards)
2709}
2710
2711pub async fn process_show_stake_account(
2712    rpc_client: &RpcClient,
2713    config: &CliConfig<'_>,
2714    stake_account_address: &Pubkey,
2715    use_lamports_unit: bool,
2716    with_rewards: Option<usize>,
2717    use_csv: bool,
2718    starting_epoch: Option<u64>,
2719) -> ProcessResult {
2720    let stake_account = rpc_client.get_account(stake_account_address).await?;
2721    let state = get_account_stake_state(
2722        rpc_client,
2723        stake_account_address,
2724        stake_account,
2725        use_lamports_unit,
2726        with_rewards,
2727        use_csv,
2728        starting_epoch,
2729    )
2730    .await?;
2731    Ok(config.output_format.formatted_string(&state))
2732}
2733
2734pub async fn get_account_stake_state(
2735    rpc_client: &RpcClient,
2736    stake_account_address: &Pubkey,
2737    stake_account: solana_account::Account,
2738    use_lamports_unit: bool,
2739    with_rewards: Option<usize>,
2740    use_csv: bool,
2741    starting_epoch: Option<u64>,
2742) -> Result<CliStakeState, CliError> {
2743    if stake_account.owner != stake::program::id() {
2744        return Err(CliError::RpcRequestError(format!(
2745            "{stake_account_address:?} is not a stake account",
2746        )));
2747    }
2748    match stake_account.state() {
2749        Ok(stake_state) => {
2750            let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2751            let stake_history: StakeHistory = bincode::deserialize(&stake_history_account.data)
2752                .map_err(|_| {
2753                    CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2754                })?;
2755            let clock_account = rpc_client.get_account(&clock::id()).await?;
2756            let clock: Clock = from_account(&clock_account).ok_or_else(|| {
2757                CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
2758            })?;
2759            let new_rate_activation_epoch = get_feature_activation_epoch(
2760                rpc_client,
2761                &agave_feature_set::reduce_stake_warmup_cooldown::id(),
2762            )
2763            .await?;
2764            let fixed_point_activation_epoch = get_feature_activation_epoch(
2765                rpc_client,
2766                &agave_feature_set::upgrade_bpf_stake_program_to_v5_1::id(),
2767            )
2768            .await?;
2769            let use_fixed_point_stake_math = fixed_point_activation_epoch
2770                .is_some_and(|activation_epoch| clock.epoch >= activation_epoch);
2771            let rent_exempt_balance = rpc_client
2772                .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2773                .await?;
2774            let mut state = build_stake_state(
2775                stake_account.lamports,
2776                &stake_state,
2777                use_lamports_unit,
2778                &stake_history,
2779                &clock,
2780                new_rate_activation_epoch,
2781                rent_exempt_balance,
2782                use_csv,
2783                use_fixed_point_stake_math,
2784            );
2785
2786            if state.stake_type == CliStakeType::Stake
2787                && state.activation_epoch.is_some()
2788                && let Some(num_epochs) = with_rewards
2789            {
2790                state.epoch_rewards = match fetch_epoch_rewards(
2791                    rpc_client,
2792                    stake_account_address,
2793                    num_epochs,
2794                    starting_epoch,
2795                )
2796                .await
2797                {
2798                    Ok(rewards) => Some(rewards),
2799                    Err(error) => {
2800                        eprintln!("Failed to fetch epoch rewards: {error:?}");
2801                        None
2802                    }
2803                };
2804            }
2805            Ok(state)
2806        }
2807        Err(err) => Err(CliError::RpcRequestError(format!(
2808            "Account data could not be deserialized to stake state: {err}"
2809        ))),
2810    }
2811}
2812
2813pub async fn process_show_stake_history(
2814    rpc_client: &RpcClient,
2815    config: &CliConfig<'_>,
2816    use_lamports_unit: bool,
2817    limit_results: usize,
2818) -> ProcessResult {
2819    let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2820    let stake_history =
2821        bincode::deserialize::<StakeHistory>(&stake_history_account.data).map_err(|_| {
2822            CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2823        })?;
2824
2825    let limit_results = match config.output_format {
2826        OutputFormat::Json | OutputFormat::JsonCompact => usize::MAX,
2827        _ => {
2828            if limit_results == 0 {
2829                usize::MAX
2830            } else {
2831                limit_results
2832            }
2833        }
2834    };
2835    let mut entries: Vec<CliStakeHistoryEntry> = vec![];
2836    for entry in stake_history.deref().iter().take(limit_results) {
2837        entries.push(entry.into());
2838    }
2839    let stake_history_output = CliStakeHistory {
2840        entries,
2841        use_lamports_unit,
2842    };
2843    Ok(config.output_format.formatted_string(&stake_history_output))
2844}
2845
2846#[allow(clippy::too_many_arguments)]
2847pub async fn process_delegate_stake(
2848    rpc_client: &RpcClient,
2849    config: &CliConfig<'_>,
2850    stake_account_pubkey: &Pubkey,
2851    vote_account_pubkey: &Pubkey,
2852    stake_authority: SignerIndex,
2853    force: bool,
2854    sign_only: bool,
2855    dump_transaction_message: bool,
2856    blockhash_query: &BlockhashQuery,
2857    nonce_account: Option<Pubkey>,
2858    nonce_authority: SignerIndex,
2859    memo: Option<&String>,
2860    fee_payer: SignerIndex,
2861    compute_unit_price: Option<u64>,
2862) -> ProcessResult {
2863    check_unique_pubkeys(
2864        (&config.signers[0].pubkey(), "cli keypair".to_string()),
2865        (stake_account_pubkey, "stake_account_pubkey".to_string()),
2866    )?;
2867    let stake_authority = config.signers[stake_authority];
2868
2869    if !sign_only {
2870        // Sanity check the vote account to ensure it is attached to a validator that has recently
2871        // voted at the tip of the ledger
2872        let get_vote_accounts_config = RpcGetVoteAccountsConfig {
2873            vote_pubkey: Some(vote_account_pubkey.to_string()),
2874            keep_unstaked_delinquents: Some(true),
2875            commitment: Some(rpc_client.commitment()),
2876            ..RpcGetVoteAccountsConfig::default()
2877        };
2878        let RpcVoteAccountStatus {
2879            current,
2880            delinquent,
2881        } = rpc_client
2882            .get_vote_accounts_with_config(get_vote_accounts_config)
2883            .await?;
2884        // filter should return at most one result
2885        let rpc_vote_account =
2886            current
2887                .first()
2888                .or_else(|| delinquent.first())
2889                .ok_or(CliError::RpcRequestError(format!(
2890                    "Vote account not found: {vote_account_pubkey}"
2891                )))?;
2892
2893        let activated_stake = rpc_vote_account.activated_stake;
2894        let root_slot = rpc_vote_account.root_slot;
2895        let min_root_slot = rpc_client
2896            .get_slot()
2897            .await
2898            .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
2899        let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
2900            Ok(())
2901        } else if root_slot == 0 {
2902            Err(CliError::BadParameter(
2903                "Unable to delegate. Vote account has no root slot".to_string(),
2904            ))
2905        } else {
2906            Err(CliError::DynamicProgramError(format!(
2907                "Unable to delegate.  Vote account appears delinquent because its current root \
2908                 slot, {root_slot}, is less than {min_root_slot}"
2909            )))
2910        };
2911
2912        if let Err(err) = &sanity_check_result {
2913            if !force {
2914                sanity_check_result?;
2915            } else {
2916                println!("--force supplied, ignoring: {err}");
2917            }
2918        }
2919
2920        if !force {
2921            let stake_account = rpc_client.get_account(stake_account_pubkey).await?;
2922            if stake_account.owner != stake::program::id() {
2923                return Err(CliError::BadParameter(format!(
2924                    "{stake_account_pubkey} is not a stake account",
2925                ))
2926                .into());
2927            }
2928
2929            let rent_exempt_balance = rpc_client
2930                .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2931                .await?;
2932            let minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2933            let minimum_total_lamports = rent_exempt_balance.saturating_add(minimum_delegation);
2934            let stake_account_lamports = stake_account.lamports;
2935
2936            if stake_account_lamports < minimum_total_lamports {
2937                return Err(CliError::BadParameter(format!(
2938                    "need at least {minimum_total_lamports} lamports to delegate this stake \
2939                     account, available lamports: {stake_account_lamports}"
2940                ))
2941                .into());
2942            }
2943        }
2944    }
2945
2946    let recent_blockhash = blockhash_query
2947        .get_blockhash(rpc_client, config.commitment)
2948        .await?;
2949
2950    // DelegateStake parses a VoteState, which may change between simulation and execution
2951    let compute_unit_limit = match blockhash_query {
2952        BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2953        BlockhashQuery::Rpc(_) => ComputeUnitLimit::SimulatedWithExtraPercentage(5),
2954    };
2955    let ixs = vec![stake_instruction::delegate_stake(
2956        stake_account_pubkey,
2957        &stake_authority.pubkey(),
2958        vote_account_pubkey,
2959    )]
2960    .with_memo(memo)
2961    .with_compute_unit_config(&ComputeUnitConfig {
2962        compute_unit_price,
2963        compute_unit_limit,
2964    });
2965
2966    let nonce_authority = config.signers[nonce_authority];
2967    let fee_payer = config.signers[fee_payer];
2968
2969    let mut message = if let Some(nonce_account) = &nonce_account {
2970        Message::new_with_nonce(
2971            ixs,
2972            Some(&fee_payer.pubkey()),
2973            nonce_account,
2974            &nonce_authority.pubkey(),
2975        )
2976    } else {
2977        Message::new(&ixs, Some(&fee_payer.pubkey()))
2978    };
2979    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2980    let mut tx = Transaction::new_unsigned(message);
2981
2982    if sign_only {
2983        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2984        return_signers_with_config(
2985            &tx,
2986            &config.output_format,
2987            &ReturnSignersConfig {
2988                dump_transaction_message,
2989            },
2990        )
2991    } else {
2992        tx.try_sign(&config.signers, recent_blockhash)?;
2993        if let Some(nonce_account) = &nonce_account {
2994            let nonce_account =
2995                solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2996                    rpc_client,
2997                    nonce_account,
2998                    config.commitment,
2999                )
3000                .await?;
3001            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
3002        }
3003        check_account_for_fee_with_commitment(
3004            rpc_client,
3005            &tx.message.account_keys[0],
3006            &tx.message,
3007            config.commitment,
3008        )
3009        .await?;
3010        let result = rpc_client
3011            .send_and_confirm_transaction_with_spinner_and_config(
3012                &tx,
3013                config.commitment,
3014                config.send_transaction_config,
3015            )
3016            .await;
3017        log_instruction_custom_error::<StakeError>(result, config)
3018    }
3019}
3020
3021pub async fn process_stake_minimum_delegation(
3022    rpc_client: &RpcClient,
3023    config: &CliConfig<'_>,
3024    use_lamports_unit: bool,
3025) -> ProcessResult {
3026    let stake_minimum_delegation = rpc_client
3027        .get_stake_minimum_delegation_with_commitment(config.commitment)
3028        .await?;
3029
3030    let stake_minimum_delegation_output = CliBalance {
3031        lamports: stake_minimum_delegation,
3032        config: BuildBalanceMessageConfig {
3033            use_lamports_unit,
3034            show_unit: true,
3035            trim_trailing_zeros: true,
3036        },
3037    };
3038
3039    Ok(config
3040        .output_format
3041        .formatted_string(&stake_minimum_delegation_output))
3042}
3043
3044#[cfg(test)]
3045mod tests {
3046    use {
3047        super::*,
3048        crate::{clap_app::get_clap_app, cli::parse_command},
3049        solana_hash::Hash,
3050        solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair},
3051        solana_presigner::Presigner,
3052        solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
3053        solana_signer::Signer,
3054        tempfile::NamedTempFile,
3055    };
3056
3057    fn make_tmp_file() -> (String, NamedTempFile) {
3058        let tmp_file = NamedTempFile::new().unwrap();
3059        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
3060    }
3061
3062    #[test]
3063    #[allow(clippy::cognitive_complexity)]
3064    fn test_parse_command() {
3065        let test_commands = get_clap_app("test", "desc", "version");
3066        let default_keypair = Keypair::new();
3067        let (default_keypair_file, mut tmp_file) = make_tmp_file();
3068        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
3069        let default_signer = DefaultSigner::new("", &default_keypair_file);
3070        let (keypair_file, mut tmp_file) = make_tmp_file();
3071        let stake_account_keypair = Keypair::new();
3072        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
3073        let stake_account_pubkey = stake_account_keypair.pubkey();
3074        let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
3075        let stake_authority_keypair = Keypair::new();
3076        write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
3077        let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
3078        let custodian_keypair = Keypair::new();
3079        write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
3080
3081        // stake-authorize subcommand
3082        let stake_account_string = stake_account_pubkey.to_string();
3083        let new_stake_authority = Pubkey::from([1u8; 32]);
3084        let new_stake_string = new_stake_authority.to_string();
3085        let new_withdraw_authority = Pubkey::from([2u8; 32]);
3086        let new_withdraw_string = new_withdraw_authority.to_string();
3087        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3088            "test",
3089            "stake-authorize",
3090            &stake_account_string,
3091            "--new-stake-authority",
3092            &new_stake_string,
3093            "--new-withdraw-authority",
3094            &new_withdraw_string,
3095        ]);
3096        assert_eq!(
3097            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3098            CliCommandInfo {
3099                command: CliCommand::StakeAuthorize {
3100                    stake_account_pubkey,
3101                    new_authorizations: vec![
3102                        StakeAuthorizationIndexed {
3103                            authorization_type: StakeAuthorize::Staker,
3104                            new_authority_pubkey: new_stake_authority,
3105                            authority: 0,
3106                            new_authority_signer: None,
3107                        },
3108                        StakeAuthorizationIndexed {
3109                            authorization_type: StakeAuthorize::Withdrawer,
3110                            new_authority_pubkey: new_withdraw_authority,
3111                            authority: 0,
3112                            new_authority_signer: None,
3113                        },
3114                    ],
3115                    sign_only: false,
3116                    dump_transaction_message: false,
3117                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3118                    nonce_account: None,
3119                    nonce_authority: 0,
3120                    memo: None,
3121                    fee_payer: 0,
3122                    custodian: None,
3123                    no_wait: false,
3124                    compute_unit_price: None,
3125                },
3126                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3127            },
3128        );
3129        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3130        let withdraw_authority_keypair = Keypair::new();
3131        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3132        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3133            "test",
3134            "stake-authorize",
3135            &stake_account_string,
3136            "--new-stake-authority",
3137            &new_stake_string,
3138            "--new-withdraw-authority",
3139            &new_withdraw_string,
3140            "--stake-authority",
3141            &stake_authority_keypair_file,
3142            "--withdraw-authority",
3143            &withdraw_authority_keypair_file,
3144        ]);
3145        assert_eq!(
3146            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3147            CliCommandInfo {
3148                command: CliCommand::StakeAuthorize {
3149                    stake_account_pubkey,
3150                    new_authorizations: vec![
3151                        StakeAuthorizationIndexed {
3152                            authorization_type: StakeAuthorize::Staker,
3153                            new_authority_pubkey: new_stake_authority,
3154                            authority: 1,
3155                            new_authority_signer: None,
3156                        },
3157                        StakeAuthorizationIndexed {
3158                            authorization_type: StakeAuthorize::Withdrawer,
3159                            new_authority_pubkey: new_withdraw_authority,
3160                            authority: 2,
3161                            new_authority_signer: None,
3162                        },
3163                    ],
3164                    sign_only: false,
3165                    dump_transaction_message: false,
3166                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3167                    nonce_account: None,
3168                    nonce_authority: 0,
3169                    memo: None,
3170                    fee_payer: 0,
3171                    custodian: None,
3172                    no_wait: false,
3173                    compute_unit_price: None,
3174                },
3175                signers: vec![
3176                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3177                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3178                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3179                ],
3180            },
3181        );
3182        // Withdraw authority may set both new authorities
3183        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3184            "test",
3185            "stake-authorize",
3186            &stake_account_string,
3187            "--new-stake-authority",
3188            &new_stake_string,
3189            "--new-withdraw-authority",
3190            &new_withdraw_string,
3191            "--withdraw-authority",
3192            &withdraw_authority_keypair_file,
3193        ]);
3194        assert_eq!(
3195            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3196            CliCommandInfo {
3197                command: CliCommand::StakeAuthorize {
3198                    stake_account_pubkey,
3199                    new_authorizations: vec![
3200                        StakeAuthorizationIndexed {
3201                            authorization_type: StakeAuthorize::Staker,
3202                            new_authority_pubkey: new_stake_authority,
3203                            authority: 1,
3204                            new_authority_signer: None,
3205                        },
3206                        StakeAuthorizationIndexed {
3207                            authorization_type: StakeAuthorize::Withdrawer,
3208                            new_authority_pubkey: new_withdraw_authority,
3209                            authority: 1,
3210                            new_authority_signer: None,
3211                        },
3212                    ],
3213                    sign_only: false,
3214                    dump_transaction_message: false,
3215                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3216                    nonce_account: None,
3217                    nonce_authority: 0,
3218                    memo: None,
3219                    fee_payer: 0,
3220                    custodian: None,
3221                    no_wait: false,
3222                    compute_unit_price: None,
3223                },
3224                signers: vec![
3225                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3226                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3227                ],
3228            },
3229        );
3230        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3231            "test",
3232            "stake-authorize",
3233            &stake_account_string,
3234            "--new-stake-authority",
3235            &new_stake_string,
3236        ]);
3237        assert_eq!(
3238            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3239            CliCommandInfo {
3240                command: CliCommand::StakeAuthorize {
3241                    stake_account_pubkey,
3242                    new_authorizations: vec![StakeAuthorizationIndexed {
3243                        authorization_type: StakeAuthorize::Staker,
3244                        new_authority_pubkey: new_stake_authority,
3245                        authority: 0,
3246                        new_authority_signer: None,
3247                    }],
3248                    sign_only: false,
3249                    dump_transaction_message: false,
3250                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3251                    nonce_account: None,
3252                    nonce_authority: 0,
3253                    memo: None,
3254                    fee_payer: 0,
3255                    custodian: None,
3256                    no_wait: false,
3257                    compute_unit_price: None,
3258                },
3259                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3260            },
3261        );
3262        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3263            "test",
3264            "stake-authorize",
3265            &stake_account_string,
3266            "--new-stake-authority",
3267            &new_stake_string,
3268            "--stake-authority",
3269            &stake_authority_keypair_file,
3270        ]);
3271        assert_eq!(
3272            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3273            CliCommandInfo {
3274                command: CliCommand::StakeAuthorize {
3275                    stake_account_pubkey,
3276                    new_authorizations: vec![StakeAuthorizationIndexed {
3277                        authorization_type: StakeAuthorize::Staker,
3278                        new_authority_pubkey: new_stake_authority,
3279                        authority: 1,
3280                        new_authority_signer: None,
3281                    }],
3282                    sign_only: false,
3283                    dump_transaction_message: false,
3284                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3285                    nonce_account: None,
3286                    nonce_authority: 0,
3287                    memo: None,
3288                    fee_payer: 0,
3289                    custodian: None,
3290                    no_wait: false,
3291                    compute_unit_price: None,
3292                },
3293                signers: vec![
3294                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3295                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3296                ],
3297            },
3298        );
3299        // Withdraw authority may set new stake authority
3300        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3301            "test",
3302            "stake-authorize",
3303            &stake_account_string,
3304            "--new-stake-authority",
3305            &new_stake_string,
3306            "--withdraw-authority",
3307            &withdraw_authority_keypair_file,
3308        ]);
3309        assert_eq!(
3310            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3311            CliCommandInfo {
3312                command: CliCommand::StakeAuthorize {
3313                    stake_account_pubkey,
3314                    new_authorizations: vec![StakeAuthorizationIndexed {
3315                        authorization_type: StakeAuthorize::Staker,
3316                        new_authority_pubkey: new_stake_authority,
3317                        authority: 1,
3318                        new_authority_signer: None,
3319                    }],
3320                    sign_only: false,
3321                    dump_transaction_message: false,
3322                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3323                    nonce_account: None,
3324                    nonce_authority: 0,
3325                    memo: None,
3326                    fee_payer: 0,
3327                    custodian: None,
3328                    no_wait: false,
3329                    compute_unit_price: None,
3330                },
3331                signers: vec![
3332                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3333                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3334                ],
3335            },
3336        );
3337        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3338            "test",
3339            "stake-authorize",
3340            &stake_account_string,
3341            "--new-withdraw-authority",
3342            &new_withdraw_string,
3343        ]);
3344        assert_eq!(
3345            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3346            CliCommandInfo {
3347                command: CliCommand::StakeAuthorize {
3348                    stake_account_pubkey,
3349                    new_authorizations: vec![StakeAuthorizationIndexed {
3350                        authorization_type: StakeAuthorize::Withdrawer,
3351                        new_authority_pubkey: new_withdraw_authority,
3352                        authority: 0,
3353                        new_authority_signer: None,
3354                    }],
3355                    sign_only: false,
3356                    dump_transaction_message: false,
3357                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3358                    nonce_account: None,
3359                    nonce_authority: 0,
3360                    memo: None,
3361                    fee_payer: 0,
3362                    custodian: None,
3363                    no_wait: false,
3364                    compute_unit_price: None,
3365                },
3366                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3367            },
3368        );
3369        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3370            "test",
3371            "stake-authorize",
3372            &stake_account_string,
3373            "--new-withdraw-authority",
3374            &new_withdraw_string,
3375            "--withdraw-authority",
3376            &withdraw_authority_keypair_file,
3377        ]);
3378        assert_eq!(
3379            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3380            CliCommandInfo {
3381                command: CliCommand::StakeAuthorize {
3382                    stake_account_pubkey,
3383                    new_authorizations: vec![StakeAuthorizationIndexed {
3384                        authorization_type: StakeAuthorize::Withdrawer,
3385                        new_authority_pubkey: new_withdraw_authority,
3386                        authority: 1,
3387                        new_authority_signer: None,
3388                    }],
3389                    sign_only: false,
3390                    dump_transaction_message: false,
3391                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3392                    nonce_account: None,
3393                    nonce_authority: 0,
3394                    memo: None,
3395                    fee_payer: 0,
3396                    custodian: None,
3397                    no_wait: false,
3398                    compute_unit_price: None,
3399                },
3400                signers: vec![
3401                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3402                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3403                ],
3404            },
3405        );
3406
3407        // Test Authorize Subcommand w/ no-wait
3408        let test_authorize = test_commands.clone().get_matches_from(vec![
3409            "test",
3410            "stake-authorize",
3411            &stake_account_string,
3412            "--new-stake-authority",
3413            &stake_account_string,
3414            "--no-wait",
3415        ]);
3416        assert_eq!(
3417            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3418            CliCommandInfo {
3419                command: CliCommand::StakeAuthorize {
3420                    stake_account_pubkey,
3421                    new_authorizations: vec![StakeAuthorizationIndexed {
3422                        authorization_type: StakeAuthorize::Staker,
3423                        new_authority_pubkey: stake_account_pubkey,
3424                        authority: 0,
3425                        new_authority_signer: None,
3426                    }],
3427                    sign_only: false,
3428                    dump_transaction_message: false,
3429                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3430                    nonce_account: None,
3431                    nonce_authority: 0,
3432                    memo: None,
3433                    fee_payer: 0,
3434                    custodian: None,
3435                    no_wait: true,
3436                    compute_unit_price: None,
3437                },
3438                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3439            }
3440        );
3441
3442        // stake-authorize-checked subcommand
3443        let (authority_keypair_file, mut tmp_file) = make_tmp_file();
3444        let authority_keypair = Keypair::new();
3445        write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
3446        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3447            "test",
3448            "stake-authorize-checked",
3449            &stake_account_string,
3450            "--new-stake-authority",
3451            &authority_keypair_file,
3452            "--new-withdraw-authority",
3453            &authority_keypair_file,
3454        ]);
3455        assert_eq!(
3456            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3457            CliCommandInfo {
3458                command: CliCommand::StakeAuthorize {
3459                    stake_account_pubkey,
3460                    new_authorizations: vec![
3461                        StakeAuthorizationIndexed {
3462                            authorization_type: StakeAuthorize::Staker,
3463                            new_authority_pubkey: authority_keypair.pubkey(),
3464                            authority: 0,
3465                            new_authority_signer: Some(1),
3466                        },
3467                        StakeAuthorizationIndexed {
3468                            authorization_type: StakeAuthorize::Withdrawer,
3469                            new_authority_pubkey: authority_keypair.pubkey(),
3470                            authority: 0,
3471                            new_authority_signer: Some(1),
3472                        },
3473                    ],
3474                    sign_only: false,
3475                    dump_transaction_message: false,
3476                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3477                    nonce_account: None,
3478                    nonce_authority: 0,
3479                    memo: None,
3480                    fee_payer: 0,
3481                    custodian: None,
3482                    no_wait: false,
3483                    compute_unit_price: None,
3484                },
3485                signers: vec![
3486                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3487                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3488                ],
3489            },
3490        );
3491        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3492        let withdraw_authority_keypair = Keypair::new();
3493        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3494        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3495            "test",
3496            "stake-authorize-checked",
3497            &stake_account_string,
3498            "--new-stake-authority",
3499            &authority_keypair_file,
3500            "--new-withdraw-authority",
3501            &authority_keypair_file,
3502            "--stake-authority",
3503            &stake_authority_keypair_file,
3504            "--withdraw-authority",
3505            &withdraw_authority_keypair_file,
3506        ]);
3507        assert_eq!(
3508            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3509            CliCommandInfo {
3510                command: CliCommand::StakeAuthorize {
3511                    stake_account_pubkey,
3512                    new_authorizations: vec![
3513                        StakeAuthorizationIndexed {
3514                            authorization_type: StakeAuthorize::Staker,
3515                            new_authority_pubkey: authority_keypair.pubkey(),
3516                            authority: 1,
3517                            new_authority_signer: Some(2),
3518                        },
3519                        StakeAuthorizationIndexed {
3520                            authorization_type: StakeAuthorize::Withdrawer,
3521                            new_authority_pubkey: authority_keypair.pubkey(),
3522                            authority: 3,
3523                            new_authority_signer: Some(2),
3524                        },
3525                    ],
3526                    sign_only: false,
3527                    dump_transaction_message: false,
3528                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3529                    nonce_account: None,
3530                    nonce_authority: 0,
3531                    memo: None,
3532                    fee_payer: 0,
3533                    custodian: None,
3534                    no_wait: false,
3535                    compute_unit_price: None,
3536                },
3537                signers: vec![
3538                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3539                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3540                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3541                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3542                ],
3543            },
3544        );
3545        // Withdraw authority may set both new authorities
3546        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3547            "test",
3548            "stake-authorize-checked",
3549            &stake_account_string,
3550            "--new-stake-authority",
3551            &authority_keypair_file,
3552            "--new-withdraw-authority",
3553            &authority_keypair_file,
3554            "--withdraw-authority",
3555            &withdraw_authority_keypair_file,
3556        ]);
3557        assert_eq!(
3558            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3559            CliCommandInfo {
3560                command: CliCommand::StakeAuthorize {
3561                    stake_account_pubkey,
3562                    new_authorizations: vec![
3563                        StakeAuthorizationIndexed {
3564                            authorization_type: StakeAuthorize::Staker,
3565                            new_authority_pubkey: authority_keypair.pubkey(),
3566                            authority: 1,
3567                            new_authority_signer: Some(2),
3568                        },
3569                        StakeAuthorizationIndexed {
3570                            authorization_type: StakeAuthorize::Withdrawer,
3571                            new_authority_pubkey: authority_keypair.pubkey(),
3572                            authority: 1,
3573                            new_authority_signer: Some(2),
3574                        },
3575                    ],
3576                    sign_only: false,
3577                    dump_transaction_message: false,
3578                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3579                    nonce_account: None,
3580                    nonce_authority: 0,
3581                    memo: None,
3582                    fee_payer: 0,
3583                    custodian: None,
3584                    no_wait: false,
3585                    compute_unit_price: None,
3586                },
3587                signers: vec![
3588                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3589                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3590                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3591                ],
3592            },
3593        );
3594        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3595            "test",
3596            "stake-authorize-checked",
3597            &stake_account_string,
3598            "--new-stake-authority",
3599            &authority_keypair_file,
3600        ]);
3601        assert_eq!(
3602            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3603            CliCommandInfo {
3604                command: CliCommand::StakeAuthorize {
3605                    stake_account_pubkey,
3606                    new_authorizations: vec![StakeAuthorizationIndexed {
3607                        authorization_type: StakeAuthorize::Staker,
3608                        new_authority_pubkey: authority_keypair.pubkey(),
3609                        authority: 0,
3610                        new_authority_signer: Some(1),
3611                    }],
3612                    sign_only: false,
3613                    dump_transaction_message: false,
3614                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3615                    nonce_account: None,
3616                    nonce_authority: 0,
3617                    memo: None,
3618                    fee_payer: 0,
3619                    custodian: None,
3620                    no_wait: false,
3621                    compute_unit_price: None,
3622                },
3623                signers: vec![
3624                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3625                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3626                ],
3627            },
3628        );
3629        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3630            "test",
3631            "stake-authorize-checked",
3632            &stake_account_string,
3633            "--new-stake-authority",
3634            &authority_keypair_file,
3635            "--stake-authority",
3636            &stake_authority_keypair_file,
3637        ]);
3638        assert_eq!(
3639            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3640            CliCommandInfo {
3641                command: CliCommand::StakeAuthorize {
3642                    stake_account_pubkey,
3643                    new_authorizations: vec![StakeAuthorizationIndexed {
3644                        authorization_type: StakeAuthorize::Staker,
3645                        new_authority_pubkey: authority_keypair.pubkey(),
3646                        authority: 1,
3647                        new_authority_signer: Some(2),
3648                    }],
3649                    sign_only: false,
3650                    dump_transaction_message: false,
3651                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3652                    nonce_account: None,
3653                    nonce_authority: 0,
3654                    memo: None,
3655                    fee_payer: 0,
3656                    custodian: None,
3657                    no_wait: false,
3658                    compute_unit_price: None,
3659                },
3660                signers: vec![
3661                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3662                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3663                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3664                ],
3665            },
3666        );
3667        // Withdraw authority may set new stake authority
3668        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3669            "test",
3670            "stake-authorize-checked",
3671            &stake_account_string,
3672            "--new-stake-authority",
3673            &authority_keypair_file,
3674            "--withdraw-authority",
3675            &withdraw_authority_keypair_file,
3676        ]);
3677        assert_eq!(
3678            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3679            CliCommandInfo {
3680                command: CliCommand::StakeAuthorize {
3681                    stake_account_pubkey,
3682                    new_authorizations: vec![StakeAuthorizationIndexed {
3683                        authorization_type: StakeAuthorize::Staker,
3684                        new_authority_pubkey: authority_keypair.pubkey(),
3685                        authority: 1,
3686                        new_authority_signer: Some(2),
3687                    }],
3688                    sign_only: false,
3689                    dump_transaction_message: false,
3690                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3691                    nonce_account: None,
3692                    nonce_authority: 0,
3693                    memo: None,
3694                    fee_payer: 0,
3695                    custodian: None,
3696                    no_wait: false,
3697                    compute_unit_price: None,
3698                },
3699                signers: vec![
3700                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3701                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3702                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3703                ],
3704            },
3705        );
3706        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3707            "test",
3708            "stake-authorize-checked",
3709            &stake_account_string,
3710            "--new-withdraw-authority",
3711            &authority_keypair_file,
3712        ]);
3713        assert_eq!(
3714            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3715            CliCommandInfo {
3716                command: CliCommand::StakeAuthorize {
3717                    stake_account_pubkey,
3718                    new_authorizations: vec![StakeAuthorizationIndexed {
3719                        authorization_type: StakeAuthorize::Withdrawer,
3720                        new_authority_pubkey: authority_keypair.pubkey(),
3721                        authority: 0,
3722                        new_authority_signer: Some(1),
3723                    }],
3724                    sign_only: false,
3725                    dump_transaction_message: false,
3726                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3727                    nonce_account: None,
3728                    nonce_authority: 0,
3729                    memo: None,
3730                    fee_payer: 0,
3731                    custodian: None,
3732                    no_wait: false,
3733                    compute_unit_price: None,
3734                },
3735                signers: vec![
3736                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3737                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3738                ],
3739            },
3740        );
3741        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3742            "test",
3743            "stake-authorize-checked",
3744            &stake_account_string,
3745            "--new-withdraw-authority",
3746            &authority_keypair_file,
3747            "--withdraw-authority",
3748            &withdraw_authority_keypair_file,
3749        ]);
3750        assert_eq!(
3751            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3752            CliCommandInfo {
3753                command: CliCommand::StakeAuthorize {
3754                    stake_account_pubkey,
3755                    new_authorizations: vec![StakeAuthorizationIndexed {
3756                        authorization_type: StakeAuthorize::Withdrawer,
3757                        new_authority_pubkey: authority_keypair.pubkey(),
3758                        authority: 1,
3759                        new_authority_signer: Some(2),
3760                    }],
3761                    sign_only: false,
3762                    dump_transaction_message: false,
3763                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3764                    nonce_account: None,
3765                    nonce_authority: 0,
3766                    memo: None,
3767                    fee_payer: 0,
3768                    custodian: None,
3769                    no_wait: false,
3770                    compute_unit_price: None,
3771                },
3772                signers: vec![
3773                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3774                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3775                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3776                ],
3777            },
3778        );
3779
3780        // Test Authorize Subcommand w/ no-wait
3781        let test_authorize = test_commands.clone().get_matches_from(vec![
3782            "test",
3783            "stake-authorize-checked",
3784            &stake_account_string,
3785            "--new-stake-authority",
3786            &authority_keypair_file,
3787            "--no-wait",
3788        ]);
3789        assert_eq!(
3790            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3791            CliCommandInfo {
3792                command: CliCommand::StakeAuthorize {
3793                    stake_account_pubkey,
3794                    new_authorizations: vec![StakeAuthorizationIndexed {
3795                        authorization_type: StakeAuthorize::Staker,
3796                        new_authority_pubkey: authority_keypair.pubkey(),
3797                        authority: 0,
3798                        new_authority_signer: Some(1),
3799                    }],
3800                    sign_only: false,
3801                    dump_transaction_message: false,
3802                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3803                    nonce_account: None,
3804                    nonce_authority: 0,
3805                    memo: None,
3806                    fee_payer: 0,
3807                    custodian: None,
3808                    no_wait: true,
3809                    compute_unit_price: None,
3810                },
3811                signers: vec![
3812                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3813                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3814                ],
3815            }
3816        );
3817
3818        // Test Authorize Subcommand w/ sign-only
3819        let blockhash = Hash::default();
3820        let blockhash_string = format!("{blockhash}");
3821        let test_authorize = test_commands.clone().get_matches_from(vec![
3822            "test",
3823            "stake-authorize",
3824            &stake_account_string,
3825            "--new-stake-authority",
3826            &stake_account_string,
3827            "--blockhash",
3828            &blockhash_string,
3829            "--sign-only",
3830        ]);
3831        assert_eq!(
3832            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3833            CliCommandInfo {
3834                command: CliCommand::StakeAuthorize {
3835                    stake_account_pubkey,
3836                    new_authorizations: vec![StakeAuthorizationIndexed {
3837                        authorization_type: StakeAuthorize::Staker,
3838                        new_authority_pubkey: stake_account_pubkey,
3839                        authority: 0,
3840                        new_authority_signer: None,
3841                    }],
3842                    sign_only: true,
3843                    dump_transaction_message: false,
3844                    blockhash_query: BlockhashQuery::Static(blockhash),
3845                    nonce_account: None,
3846                    nonce_authority: 0,
3847                    memo: None,
3848                    fee_payer: 0,
3849                    custodian: None,
3850                    no_wait: false,
3851                    compute_unit_price: None,
3852                },
3853                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3854            }
3855        );
3856        // Test Authorize Subcommand w/ offline feepayer
3857        let keypair = Keypair::new();
3858        let pubkey = keypair.pubkey();
3859        let sig = keypair.sign_message(&[0u8]);
3860        let signer = format!("{}={}", keypair.pubkey(), sig);
3861        let test_authorize = test_commands.clone().get_matches_from(vec![
3862            "test",
3863            "stake-authorize",
3864            &stake_account_string,
3865            "--new-stake-authority",
3866            &stake_account_string,
3867            "--blockhash",
3868            &blockhash_string,
3869            "--signer",
3870            &signer,
3871            "--fee-payer",
3872            &pubkey.to_string(),
3873        ]);
3874        assert_eq!(
3875            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3876            CliCommandInfo {
3877                command: CliCommand::StakeAuthorize {
3878                    stake_account_pubkey,
3879                    new_authorizations: vec![StakeAuthorizationIndexed {
3880                        authorization_type: StakeAuthorize::Staker,
3881                        new_authority_pubkey: stake_account_pubkey,
3882                        authority: 0,
3883                        new_authority_signer: None,
3884                    }],
3885                    sign_only: false,
3886                    dump_transaction_message: false,
3887                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3888                    nonce_account: None,
3889                    nonce_authority: 0,
3890                    memo: None,
3891                    fee_payer: 1,
3892                    custodian: None,
3893                    no_wait: false,
3894                    compute_unit_price: None,
3895                },
3896                signers: vec![
3897                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3898                    Box::new(Presigner::new(&pubkey, &sig))
3899                ],
3900            }
3901        );
3902        // Test Authorize Subcommand w/ offline fee payer and nonce authority
3903        let keypair2 = Keypair::new();
3904        let pubkey2 = keypair2.pubkey();
3905        let sig2 = keypair.sign_message(&[0u8]);
3906        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
3907        let nonce_account = Pubkey::from([1u8; 32]);
3908        let test_authorize = test_commands.clone().get_matches_from(vec![
3909            "test",
3910            "stake-authorize",
3911            &stake_account_string,
3912            "--new-stake-authority",
3913            &stake_account_string,
3914            "--blockhash",
3915            &blockhash_string,
3916            "--signer",
3917            &signer,
3918            "--signer",
3919            &signer2,
3920            "--fee-payer",
3921            &pubkey.to_string(),
3922            "--nonce",
3923            &nonce_account.to_string(),
3924            "--nonce-authority",
3925            &pubkey2.to_string(),
3926        ]);
3927        assert_eq!(
3928            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3929            CliCommandInfo {
3930                command: CliCommand::StakeAuthorize {
3931                    stake_account_pubkey,
3932                    new_authorizations: vec![StakeAuthorizationIndexed {
3933                        authorization_type: StakeAuthorize::Staker,
3934                        new_authority_pubkey: stake_account_pubkey,
3935                        authority: 0,
3936                        new_authority_signer: None,
3937                    }],
3938                    sign_only: false,
3939                    dump_transaction_message: false,
3940                    blockhash_query: BlockhashQuery::Validated(
3941                        Source::NonceAccount(nonce_account),
3942                        blockhash
3943                    ),
3944                    nonce_account: Some(nonce_account),
3945                    nonce_authority: 2,
3946                    memo: None,
3947                    fee_payer: 1,
3948                    custodian: None,
3949                    no_wait: false,
3950                    compute_unit_price: None,
3951                },
3952                signers: vec![
3953                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3954                    Box::new(Presigner::new(&pubkey, &sig)),
3955                    Box::new(Presigner::new(&pubkey2, &sig2)),
3956                ],
3957            }
3958        );
3959        // Test Authorize Subcommand w/ blockhash
3960        let test_authorize = test_commands.clone().get_matches_from(vec![
3961            "test",
3962            "stake-authorize",
3963            &stake_account_string,
3964            "--new-stake-authority",
3965            &stake_account_string,
3966            "--blockhash",
3967            &blockhash_string,
3968        ]);
3969        assert_eq!(
3970            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3971            CliCommandInfo {
3972                command: CliCommand::StakeAuthorize {
3973                    stake_account_pubkey,
3974                    new_authorizations: vec![StakeAuthorizationIndexed {
3975                        authorization_type: StakeAuthorize::Staker,
3976                        new_authority_pubkey: stake_account_pubkey,
3977                        authority: 0,
3978                        new_authority_signer: None,
3979                    }],
3980                    sign_only: false,
3981                    dump_transaction_message: false,
3982                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3983                    nonce_account: None,
3984                    nonce_authority: 0,
3985                    memo: None,
3986                    fee_payer: 0,
3987                    custodian: None,
3988                    no_wait: false,
3989                    compute_unit_price: None,
3990                },
3991                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3992            }
3993        );
3994        // Test Authorize Subcommand w/ nonce
3995        let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
3996        let nonce_authority_keypair = Keypair::new();
3997        write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
3998        let nonce_account_pubkey = nonce_authority_keypair.pubkey();
3999        let nonce_account_string = nonce_account_pubkey.to_string();
4000        let test_authorize = test_commands.clone().get_matches_from(vec![
4001            "test",
4002            "stake-authorize",
4003            &stake_account_string,
4004            "--new-stake-authority",
4005            &stake_account_string,
4006            "--blockhash",
4007            &blockhash_string,
4008            "--nonce",
4009            &nonce_account_string,
4010            "--nonce-authority",
4011            &nonce_keypair_file,
4012        ]);
4013        assert_eq!(
4014            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4015            CliCommandInfo {
4016                command: CliCommand::StakeAuthorize {
4017                    stake_account_pubkey,
4018                    new_authorizations: vec![StakeAuthorizationIndexed {
4019                        authorization_type: StakeAuthorize::Staker,
4020                        new_authority_pubkey: stake_account_pubkey,
4021                        authority: 0,
4022                        new_authority_signer: None,
4023                    }],
4024                    sign_only: false,
4025                    dump_transaction_message: false,
4026                    blockhash_query: BlockhashQuery::Validated(
4027                        Source::NonceAccount(nonce_account_pubkey),
4028                        blockhash
4029                    ),
4030                    nonce_account: Some(nonce_account_pubkey),
4031                    nonce_authority: 1,
4032                    memo: None,
4033                    fee_payer: 0,
4034                    custodian: None,
4035                    no_wait: false,
4036                    compute_unit_price: None,
4037                },
4038                signers: vec![
4039                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4040                    Box::new(nonce_authority_keypair)
4041                ],
4042            }
4043        );
4044        // Test Authorize Subcommand w/ fee-payer
4045        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4046        let fee_payer_keypair = Keypair::new();
4047        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4048        let fee_payer_pubkey = fee_payer_keypair.pubkey();
4049        let fee_payer_string = fee_payer_pubkey.to_string();
4050        let test_authorize = test_commands.clone().get_matches_from(vec![
4051            "test",
4052            "stake-authorize",
4053            &stake_account_string,
4054            "--new-stake-authority",
4055            &stake_account_string,
4056            "--fee-payer",
4057            &fee_payer_keypair_file,
4058        ]);
4059        assert_eq!(
4060            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4061            CliCommandInfo {
4062                command: CliCommand::StakeAuthorize {
4063                    stake_account_pubkey,
4064                    new_authorizations: vec![StakeAuthorizationIndexed {
4065                        authorization_type: StakeAuthorize::Staker,
4066                        new_authority_pubkey: stake_account_pubkey,
4067                        authority: 0,
4068                        new_authority_signer: None,
4069                    }],
4070                    sign_only: false,
4071                    dump_transaction_message: false,
4072                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4073                    nonce_account: None,
4074                    nonce_authority: 0,
4075                    memo: None,
4076                    fee_payer: 1,
4077                    custodian: None,
4078                    no_wait: false,
4079                    compute_unit_price: None,
4080                },
4081                signers: vec![
4082                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4083                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap()),
4084                ],
4085            }
4086        );
4087        // Test Authorize Subcommand w/ absentee fee-payer
4088        let sig = fee_payer_keypair.sign_message(&[0u8]);
4089        let signer = format!("{fee_payer_string}={sig}");
4090        let test_authorize = test_commands.clone().get_matches_from(vec![
4091            "test",
4092            "stake-authorize",
4093            &stake_account_string,
4094            "--new-stake-authority",
4095            &stake_account_string,
4096            "--fee-payer",
4097            &fee_payer_string,
4098            "--blockhash",
4099            &blockhash_string,
4100            "--signer",
4101            &signer,
4102        ]);
4103        assert_eq!(
4104            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4105            CliCommandInfo {
4106                command: CliCommand::StakeAuthorize {
4107                    stake_account_pubkey,
4108                    new_authorizations: vec![StakeAuthorizationIndexed {
4109                        authorization_type: StakeAuthorize::Staker,
4110                        new_authority_pubkey: stake_account_pubkey,
4111                        authority: 0,
4112                        new_authority_signer: None,
4113                    }],
4114                    sign_only: false,
4115                    dump_transaction_message: false,
4116                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4117                    nonce_account: None,
4118                    nonce_authority: 0,
4119                    memo: None,
4120                    fee_payer: 1,
4121                    custodian: None,
4122                    no_wait: false,
4123                    compute_unit_price: None,
4124                },
4125                signers: vec![
4126                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4127                    Box::new(Presigner::new(&fee_payer_pubkey, &sig))
4128                ],
4129            }
4130        );
4131
4132        // Test CreateStakeAccount SubCommand
4133        let custodian = solana_pubkey::new_rand();
4134        let custodian_string = format!("{custodian}");
4135        let authorized = solana_pubkey::new_rand();
4136        let authorized_string = format!("{authorized}");
4137        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4138            "test",
4139            "create-stake-account",
4140            &keypair_file,
4141            "50",
4142            "--stake-authority",
4143            &authorized_string,
4144            "--withdraw-authority",
4145            &authorized_string,
4146            "--custodian",
4147            &custodian_string,
4148            "--lockup-epoch",
4149            "43",
4150        ]);
4151        assert_eq!(
4152            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4153            CliCommandInfo {
4154                command: CliCommand::CreateStakeAccount {
4155                    stake_account: 1,
4156                    seed: None,
4157                    staker: Some(authorized),
4158                    withdrawer: Some(authorized),
4159                    withdrawer_signer: None,
4160                    lockup: Lockup {
4161                        epoch: 43,
4162                        unix_timestamp: 0,
4163                        custodian,
4164                    },
4165                    amount: SpendAmount::Some(50_000_000_000),
4166                    sign_only: false,
4167                    dump_transaction_message: false,
4168                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4169                    nonce_account: None,
4170                    nonce_authority: 0,
4171                    memo: None,
4172                    fee_payer: 0,
4173                    from: 0,
4174                    compute_unit_price: None,
4175                },
4176                signers: vec![
4177                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4178                    Box::new(stake_account_keypair)
4179                ],
4180            }
4181        );
4182
4183        let (keypair_file, mut tmp_file) = make_tmp_file();
4184        let stake_account_keypair = Keypair::new();
4185        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4186        let stake_account_pubkey = stake_account_keypair.pubkey();
4187        let stake_account_string = stake_account_pubkey.to_string();
4188
4189        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4190            "test",
4191            "create-stake-account",
4192            &keypair_file,
4193            "50",
4194        ]);
4195
4196        assert_eq!(
4197            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4198            CliCommandInfo {
4199                command: CliCommand::CreateStakeAccount {
4200                    stake_account: 1,
4201                    seed: None,
4202                    staker: None,
4203                    withdrawer: None,
4204                    withdrawer_signer: None,
4205                    lockup: Lockup::default(),
4206                    amount: SpendAmount::Some(50_000_000_000),
4207                    sign_only: false,
4208                    dump_transaction_message: false,
4209                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4210                    nonce_account: None,
4211                    nonce_authority: 0,
4212                    memo: None,
4213                    fee_payer: 0,
4214                    from: 0,
4215                    compute_unit_price: None,
4216                },
4217                signers: vec![
4218                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4219                    Box::new(read_keypair_file(&keypair_file).unwrap())
4220                ],
4221            }
4222        );
4223        let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
4224        let withdrawer_keypair = Keypair::new();
4225        write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
4226        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4227            "test",
4228            "create-stake-account-checked",
4229            &keypair_file,
4230            "50",
4231            "--stake-authority",
4232            &authorized_string,
4233            "--withdraw-authority",
4234            &withdrawer_keypair_file,
4235        ]);
4236        assert_eq!(
4237            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4238            CliCommandInfo {
4239                command: CliCommand::CreateStakeAccount {
4240                    stake_account: 1,
4241                    seed: None,
4242                    staker: Some(authorized),
4243                    withdrawer: Some(withdrawer_keypair.pubkey()),
4244                    withdrawer_signer: Some(2),
4245                    lockup: Lockup::default(),
4246                    amount: SpendAmount::Some(50_000_000_000),
4247                    sign_only: false,
4248                    dump_transaction_message: false,
4249                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4250                    nonce_account: None,
4251                    nonce_authority: 0,
4252                    memo: None,
4253                    fee_payer: 0,
4254                    from: 0,
4255                    compute_unit_price: None,
4256                },
4257                signers: vec![
4258                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4259                    Box::new(stake_account_keypair),
4260                    Box::new(withdrawer_keypair),
4261                ],
4262            }
4263        );
4264
4265        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4266            "test",
4267            "create-stake-account-checked",
4268            &keypair_file,
4269            "50",
4270            "--stake-authority",
4271            &authorized_string,
4272            "--withdraw-authority",
4273            &authorized_string,
4274        ]);
4275        assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());
4276
4277        // CreateStakeAccount offline and nonce
4278        let nonce_account = Pubkey::from([1u8; 32]);
4279        let nonce_account_string = nonce_account.to_string();
4280        let offline = keypair_from_seed(&[2u8; 32]).unwrap();
4281        let offline_pubkey = offline.pubkey();
4282        let offline_string = offline_pubkey.to_string();
4283        let offline_sig = offline.sign_message(&[3u8]);
4284        let offline_signer = format!("{offline_pubkey}={offline_sig}");
4285        let nonce_hash = Hash::new_from_array([4u8; 32]);
4286        let nonce_hash_string = nonce_hash.to_string();
4287        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4288            "test",
4289            "create-stake-account",
4290            &keypair_file,
4291            "50",
4292            "--blockhash",
4293            &nonce_hash_string,
4294            "--nonce",
4295            &nonce_account_string,
4296            "--nonce-authority",
4297            &offline_string,
4298            "--fee-payer",
4299            &offline_string,
4300            "--from",
4301            &offline_string,
4302            "--signer",
4303            &offline_signer,
4304        ]);
4305
4306        assert_eq!(
4307            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4308            CliCommandInfo {
4309                command: CliCommand::CreateStakeAccount {
4310                    stake_account: 1,
4311                    seed: None,
4312                    staker: None,
4313                    withdrawer: None,
4314                    withdrawer_signer: None,
4315                    lockup: Lockup::default(),
4316                    amount: SpendAmount::Some(50_000_000_000),
4317                    sign_only: false,
4318                    dump_transaction_message: false,
4319                    blockhash_query: BlockhashQuery::Validated(
4320                        Source::NonceAccount(nonce_account),
4321                        nonce_hash
4322                    ),
4323                    nonce_account: Some(nonce_account),
4324                    nonce_authority: 0,
4325                    memo: None,
4326                    fee_payer: 0,
4327                    from: 0,
4328                    compute_unit_price: None,
4329                },
4330                signers: vec![
4331                    Box::new(Presigner::new(&offline_pubkey, &offline_sig)),
4332                    Box::new(read_keypair_file(&keypair_file).unwrap())
4333                ],
4334            }
4335        );
4336
4337        // Test DelegateStake Subcommand
4338        let vote_account_pubkey = solana_pubkey::new_rand();
4339        let vote_account_string = vote_account_pubkey.to_string();
4340        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4341            "test",
4342            "delegate-stake",
4343            &stake_account_string,
4344            &vote_account_string,
4345        ]);
4346        assert_eq!(
4347            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4348            CliCommandInfo {
4349                command: CliCommand::DelegateStake {
4350                    stake_account_pubkey,
4351                    vote_account_pubkey,
4352                    stake_authority: 0,
4353                    force: false,
4354                    sign_only: false,
4355                    dump_transaction_message: false,
4356                    blockhash_query: BlockhashQuery::default(),
4357                    nonce_account: None,
4358                    nonce_authority: 0,
4359                    memo: None,
4360                    fee_payer: 0,
4361                    compute_unit_price: None,
4362                },
4363                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4364            }
4365        );
4366
4367        // Test DelegateStake Subcommand w/ authority
4368        let vote_account_pubkey = solana_pubkey::new_rand();
4369        let vote_account_string = vote_account_pubkey.to_string();
4370        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4371            "test",
4372            "delegate-stake",
4373            &stake_account_string,
4374            &vote_account_string,
4375            "--stake-authority",
4376            &stake_authority_keypair_file,
4377        ]);
4378        assert_eq!(
4379            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4380            CliCommandInfo {
4381                command: CliCommand::DelegateStake {
4382                    stake_account_pubkey,
4383                    vote_account_pubkey,
4384                    stake_authority: 1,
4385                    force: false,
4386                    sign_only: false,
4387                    dump_transaction_message: false,
4388                    blockhash_query: BlockhashQuery::default(),
4389                    nonce_account: None,
4390                    nonce_authority: 0,
4391                    memo: None,
4392                    fee_payer: 0,
4393                    compute_unit_price: None,
4394                },
4395                signers: vec![
4396                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4397                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4398                ],
4399            }
4400        );
4401
4402        // Test DelegateStake Subcommand w/ force
4403        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4404            "test",
4405            "delegate-stake",
4406            "--force",
4407            &stake_account_string,
4408            &vote_account_string,
4409        ]);
4410        assert_eq!(
4411            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4412            CliCommandInfo {
4413                command: CliCommand::DelegateStake {
4414                    stake_account_pubkey,
4415                    vote_account_pubkey,
4416                    stake_authority: 0,
4417                    force: true,
4418                    sign_only: false,
4419                    dump_transaction_message: false,
4420                    blockhash_query: BlockhashQuery::default(),
4421                    nonce_account: None,
4422                    nonce_authority: 0,
4423                    memo: None,
4424                    fee_payer: 0,
4425                    compute_unit_price: None,
4426                },
4427                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4428            }
4429        );
4430
4431        // Test Delegate Subcommand w/ Blockhash
4432        let blockhash = Hash::default();
4433        let blockhash_string = format!("{blockhash}");
4434        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4435            "test",
4436            "delegate-stake",
4437            &stake_account_string,
4438            &vote_account_string,
4439            "--blockhash",
4440            &blockhash_string,
4441        ]);
4442        assert_eq!(
4443            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4444            CliCommandInfo {
4445                command: CliCommand::DelegateStake {
4446                    stake_account_pubkey,
4447                    vote_account_pubkey,
4448                    stake_authority: 0,
4449                    force: false,
4450                    sign_only: false,
4451                    dump_transaction_message: false,
4452                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4453                    nonce_account: None,
4454                    nonce_authority: 0,
4455                    memo: None,
4456                    fee_payer: 0,
4457                    compute_unit_price: None,
4458                },
4459                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4460            }
4461        );
4462
4463        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4464            "test",
4465            "delegate-stake",
4466            &stake_account_string,
4467            &vote_account_string,
4468            "--blockhash",
4469            &blockhash_string,
4470            "--sign-only",
4471        ]);
4472        assert_eq!(
4473            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4474            CliCommandInfo {
4475                command: CliCommand::DelegateStake {
4476                    stake_account_pubkey,
4477                    vote_account_pubkey,
4478                    stake_authority: 0,
4479                    force: false,
4480                    sign_only: true,
4481                    dump_transaction_message: false,
4482                    blockhash_query: BlockhashQuery::Static(blockhash),
4483                    nonce_account: None,
4484                    nonce_authority: 0,
4485                    memo: None,
4486                    fee_payer: 0,
4487                    compute_unit_price: None,
4488                },
4489                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4490            }
4491        );
4492
4493        // Test Delegate Subcommand w/ absent fee payer
4494        let key1 = solana_pubkey::new_rand();
4495        let sig1 = Keypair::new().sign_message(&[0u8]);
4496        let signer1 = format!("{key1}={sig1}");
4497        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4498            "test",
4499            "delegate-stake",
4500            &stake_account_string,
4501            &vote_account_string,
4502            "--blockhash",
4503            &blockhash_string,
4504            "--signer",
4505            &signer1,
4506            "--fee-payer",
4507            &key1.to_string(),
4508        ]);
4509        assert_eq!(
4510            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4511            CliCommandInfo {
4512                command: CliCommand::DelegateStake {
4513                    stake_account_pubkey,
4514                    vote_account_pubkey,
4515                    stake_authority: 0,
4516                    force: false,
4517                    sign_only: false,
4518                    dump_transaction_message: false,
4519                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4520                    nonce_account: None,
4521                    nonce_authority: 0,
4522                    memo: None,
4523                    fee_payer: 1,
4524                    compute_unit_price: None,
4525                },
4526                signers: vec![
4527                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4528                    Box::new(Presigner::new(&key1, &sig1))
4529                ],
4530            }
4531        );
4532
4533        // Test Delegate Subcommand w/ absent fee payer and absent nonce authority
4534        let key2 = solana_pubkey::new_rand();
4535        let sig2 = Keypair::new().sign_message(&[0u8]);
4536        let signer2 = format!("{key2}={sig2}");
4537        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4538            "test",
4539            "delegate-stake",
4540            &stake_account_string,
4541            &vote_account_string,
4542            "--blockhash",
4543            &blockhash_string,
4544            "--signer",
4545            &signer1,
4546            "--signer",
4547            &signer2,
4548            "--fee-payer",
4549            &key1.to_string(),
4550            "--nonce",
4551            &nonce_account.to_string(),
4552            "--nonce-authority",
4553            &key2.to_string(),
4554        ]);
4555        assert_eq!(
4556            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4557            CliCommandInfo {
4558                command: CliCommand::DelegateStake {
4559                    stake_account_pubkey,
4560                    vote_account_pubkey,
4561                    stake_authority: 0,
4562                    force: false,
4563                    sign_only: false,
4564                    dump_transaction_message: false,
4565                    blockhash_query: BlockhashQuery::Validated(
4566                        Source::NonceAccount(nonce_account),
4567                        blockhash
4568                    ),
4569                    nonce_account: Some(nonce_account),
4570                    nonce_authority: 2,
4571                    memo: None,
4572                    fee_payer: 1,
4573                    compute_unit_price: None,
4574                },
4575                signers: vec![
4576                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4577                    Box::new(Presigner::new(&key1, &sig1)),
4578                    Box::new(Presigner::new(&key2, &sig2)),
4579                ],
4580            }
4581        );
4582
4583        // Test Delegate Subcommand w/ present fee-payer
4584        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4585        let fee_payer_keypair = Keypair::new();
4586        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4587        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4588            "test",
4589            "delegate-stake",
4590            &stake_account_string,
4591            &vote_account_string,
4592            "--fee-payer",
4593            &fee_payer_keypair_file,
4594        ]);
4595        assert_eq!(
4596            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4597            CliCommandInfo {
4598                command: CliCommand::DelegateStake {
4599                    stake_account_pubkey,
4600                    vote_account_pubkey,
4601                    stake_authority: 0,
4602                    force: false,
4603                    sign_only: false,
4604                    dump_transaction_message: false,
4605                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4606                    nonce_account: None,
4607                    nonce_authority: 0,
4608                    memo: None,
4609                    fee_payer: 1,
4610                    compute_unit_price: None,
4611                },
4612                signers: vec![
4613                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4614                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
4615                ],
4616            }
4617        );
4618
4619        // Test WithdrawStake Subcommand
4620        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4621            "test",
4622            "withdraw-stake",
4623            &stake_account_string,
4624            &stake_account_string,
4625            "42",
4626        ]);
4627
4628        assert_eq!(
4629            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4630            CliCommandInfo {
4631                command: CliCommand::WithdrawStake {
4632                    stake_account_pubkey,
4633                    destination_account_pubkey: stake_account_pubkey,
4634                    amount: SpendAmount::Some(42_000_000_000),
4635                    withdraw_authority: 0,
4636                    custodian: None,
4637                    sign_only: false,
4638                    dump_transaction_message: false,
4639                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4640                    nonce_account: None,
4641                    nonce_authority: 0,
4642                    memo: None,
4643                    seed: None,
4644                    fee_payer: 0,
4645                    compute_unit_price: None,
4646                },
4647                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4648            }
4649        );
4650
4651        // Test WithdrawStake Subcommand w/ AVAILABLE amount
4652        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4653            "test",
4654            "withdraw-stake",
4655            &stake_account_string,
4656            &stake_account_string,
4657            "AVAILABLE",
4658        ]);
4659
4660        assert_eq!(
4661            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4662            CliCommandInfo {
4663                command: CliCommand::WithdrawStake {
4664                    stake_account_pubkey,
4665                    destination_account_pubkey: stake_account_pubkey,
4666                    amount: SpendAmount::Available,
4667                    withdraw_authority: 0,
4668                    custodian: None,
4669                    sign_only: false,
4670                    dump_transaction_message: false,
4671                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4672                    nonce_account: None,
4673                    nonce_authority: 0,
4674                    memo: None,
4675                    seed: None,
4676                    fee_payer: 0,
4677                    compute_unit_price: None,
4678                },
4679                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4680            }
4681        );
4682
4683        // Test WithdrawStake Subcommand w/ ComputeUnitPrice
4684        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4685            "test",
4686            "withdraw-stake",
4687            &stake_account_string,
4688            &stake_account_string,
4689            "42",
4690            "--with-compute-unit-price",
4691            "99",
4692        ]);
4693
4694        assert_eq!(
4695            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4696            CliCommandInfo {
4697                command: CliCommand::WithdrawStake {
4698                    stake_account_pubkey,
4699                    destination_account_pubkey: stake_account_pubkey,
4700                    amount: SpendAmount::Some(42_000_000_000),
4701                    withdraw_authority: 0,
4702                    custodian: None,
4703                    sign_only: false,
4704                    dump_transaction_message: false,
4705                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4706                    nonce_account: None,
4707                    nonce_authority: 0,
4708                    memo: None,
4709                    seed: None,
4710                    fee_payer: 0,
4711                    compute_unit_price: Some(99),
4712                },
4713                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4714            }
4715        );
4716
4717        // Test WithdrawStake Subcommand w/ authority
4718        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4719            "test",
4720            "withdraw-stake",
4721            &stake_account_string,
4722            &stake_account_string,
4723            "42",
4724            "--withdraw-authority",
4725            &stake_authority_keypair_file,
4726        ]);
4727
4728        assert_eq!(
4729            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4730            CliCommandInfo {
4731                command: CliCommand::WithdrawStake {
4732                    stake_account_pubkey,
4733                    destination_account_pubkey: stake_account_pubkey,
4734                    amount: SpendAmount::Some(42_000_000_000),
4735                    withdraw_authority: 1,
4736                    custodian: None,
4737                    sign_only: false,
4738                    dump_transaction_message: false,
4739                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4740                    nonce_account: None,
4741                    nonce_authority: 0,
4742                    memo: None,
4743                    seed: None,
4744                    fee_payer: 0,
4745                    compute_unit_price: None,
4746                },
4747                signers: vec![
4748                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4749                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4750                ],
4751            }
4752        );
4753
4754        // Test WithdrawStake Subcommand w/ custodian
4755        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4756            "test",
4757            "withdraw-stake",
4758            &stake_account_string,
4759            &stake_account_string,
4760            "42",
4761            "--custodian",
4762            &custodian_keypair_file,
4763        ]);
4764
4765        assert_eq!(
4766            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4767            CliCommandInfo {
4768                command: CliCommand::WithdrawStake {
4769                    stake_account_pubkey,
4770                    destination_account_pubkey: stake_account_pubkey,
4771                    amount: SpendAmount::Some(42_000_000_000),
4772                    withdraw_authority: 0,
4773                    custodian: Some(1),
4774                    sign_only: false,
4775                    dump_transaction_message: false,
4776                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4777                    nonce_account: None,
4778                    nonce_authority: 0,
4779                    memo: None,
4780                    seed: None,
4781                    fee_payer: 0,
4782                    compute_unit_price: None,
4783                },
4784                signers: vec![
4785                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4786                    Box::new(read_keypair_file(&custodian_keypair_file).unwrap())
4787                ],
4788            }
4789        );
4790
4791        // Test WithdrawStake Subcommand w/ authority and offline nonce
4792        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4793            "test",
4794            "withdraw-stake",
4795            &stake_account_string,
4796            &stake_account_string,
4797            "42",
4798            "--withdraw-authority",
4799            &stake_authority_keypair_file,
4800            "--blockhash",
4801            &nonce_hash_string,
4802            "--nonce",
4803            &nonce_account_string,
4804            "--nonce-authority",
4805            &offline_string,
4806            "--fee-payer",
4807            &offline_string,
4808            "--signer",
4809            &offline_signer,
4810        ]);
4811
4812        assert_eq!(
4813            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4814            CliCommandInfo {
4815                command: CliCommand::WithdrawStake {
4816                    stake_account_pubkey,
4817                    destination_account_pubkey: stake_account_pubkey,
4818                    amount: SpendAmount::Some(42_000_000_000),
4819                    withdraw_authority: 0,
4820                    custodian: None,
4821                    sign_only: false,
4822                    dump_transaction_message: false,
4823                    blockhash_query: BlockhashQuery::Validated(
4824                        Source::NonceAccount(nonce_account),
4825                        nonce_hash
4826                    ),
4827                    nonce_account: Some(nonce_account),
4828                    nonce_authority: 1,
4829                    memo: None,
4830                    seed: None,
4831                    fee_payer: 1,
4832                    compute_unit_price: None,
4833                },
4834                signers: vec![
4835                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
4836                    Box::new(Presigner::new(&offline_pubkey, &offline_sig))
4837                ],
4838            }
4839        );
4840
4841        // Test DeactivateStake Subcommand
4842        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4843            "test",
4844            "deactivate-stake",
4845            &stake_account_string,
4846        ]);
4847        assert_eq!(
4848            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4849            CliCommandInfo {
4850                command: CliCommand::DeactivateStake {
4851                    stake_account_pubkey,
4852                    stake_authority: 0,
4853                    sign_only: false,
4854                    deactivate_delinquent: false,
4855                    dump_transaction_message: false,
4856                    blockhash_query: BlockhashQuery::default(),
4857                    nonce_account: None,
4858                    nonce_authority: 0,
4859                    memo: None,
4860                    seed: None,
4861                    fee_payer: 0,
4862                    compute_unit_price: None,
4863                },
4864                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4865            }
4866        );
4867
4868        // Test DeactivateStake Subcommand with delinquent flag
4869        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4870            "test",
4871            "deactivate-stake",
4872            &stake_account_string,
4873            "--delinquent",
4874        ]);
4875        assert_eq!(
4876            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4877            CliCommandInfo {
4878                command: CliCommand::DeactivateStake {
4879                    stake_account_pubkey,
4880                    stake_authority: 0,
4881                    sign_only: false,
4882                    deactivate_delinquent: true,
4883                    dump_transaction_message: false,
4884                    blockhash_query: BlockhashQuery::default(),
4885                    nonce_account: None,
4886                    nonce_authority: 0,
4887                    memo: None,
4888                    seed: None,
4889                    fee_payer: 0,
4890                    compute_unit_price: None,
4891                },
4892                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4893            }
4894        );
4895
4896        // Test DeactivateStake Subcommand w/ authority
4897        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4898            "test",
4899            "deactivate-stake",
4900            &stake_account_string,
4901            "--stake-authority",
4902            &stake_authority_keypair_file,
4903        ]);
4904        assert_eq!(
4905            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4906            CliCommandInfo {
4907                command: CliCommand::DeactivateStake {
4908                    stake_account_pubkey,
4909                    stake_authority: 1,
4910                    sign_only: false,
4911                    deactivate_delinquent: false,
4912                    dump_transaction_message: false,
4913                    blockhash_query: BlockhashQuery::default(),
4914                    nonce_account: None,
4915                    nonce_authority: 0,
4916                    memo: None,
4917                    seed: None,
4918                    fee_payer: 0,
4919                    compute_unit_price: None,
4920                },
4921                signers: vec![
4922                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4923                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4924                ],
4925            }
4926        );
4927
4928        // Test Deactivate Subcommand w/ Blockhash
4929        let blockhash = Hash::default();
4930        let blockhash_string = format!("{blockhash}");
4931        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4932            "test",
4933            "deactivate-stake",
4934            &stake_account_string,
4935            "--blockhash",
4936            &blockhash_string,
4937        ]);
4938        assert_eq!(
4939            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4940            CliCommandInfo {
4941                command: CliCommand::DeactivateStake {
4942                    stake_account_pubkey,
4943                    stake_authority: 0,
4944                    sign_only: false,
4945                    deactivate_delinquent: false,
4946                    dump_transaction_message: false,
4947                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4948                    nonce_account: None,
4949                    nonce_authority: 0,
4950                    memo: None,
4951                    seed: None,
4952                    fee_payer: 0,
4953                    compute_unit_price: None,
4954                },
4955                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4956            }
4957        );
4958
4959        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4960            "test",
4961            "deactivate-stake",
4962            &stake_account_string,
4963            "--blockhash",
4964            &blockhash_string,
4965            "--sign-only",
4966        ]);
4967        assert_eq!(
4968            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4969            CliCommandInfo {
4970                command: CliCommand::DeactivateStake {
4971                    stake_account_pubkey,
4972                    stake_authority: 0,
4973                    sign_only: true,
4974                    deactivate_delinquent: false,
4975                    dump_transaction_message: false,
4976                    blockhash_query: BlockhashQuery::Static(blockhash),
4977                    nonce_account: None,
4978                    nonce_authority: 0,
4979                    memo: None,
4980                    seed: None,
4981                    fee_payer: 0,
4982                    compute_unit_price: None,
4983                },
4984                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4985            }
4986        );
4987
4988        // Test Deactivate Subcommand w/ absent fee payer
4989        let key1 = solana_pubkey::new_rand();
4990        let sig1 = Keypair::new().sign_message(&[0u8]);
4991        let signer1 = format!("{key1}={sig1}");
4992        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4993            "test",
4994            "deactivate-stake",
4995            &stake_account_string,
4996            "--blockhash",
4997            &blockhash_string,
4998            "--signer",
4999            &signer1,
5000            "--fee-payer",
5001            &key1.to_string(),
5002        ]);
5003        assert_eq!(
5004            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5005            CliCommandInfo {
5006                command: CliCommand::DeactivateStake {
5007                    stake_account_pubkey,
5008                    stake_authority: 0,
5009                    sign_only: false,
5010                    deactivate_delinquent: false,
5011                    dump_transaction_message: false,
5012                    blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
5013                    nonce_account: None,
5014                    nonce_authority: 0,
5015                    memo: None,
5016                    seed: None,
5017                    fee_payer: 1,
5018                    compute_unit_price: None,
5019                },
5020                signers: vec![
5021                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5022                    Box::new(Presigner::new(&key1, &sig1))
5023                ],
5024            }
5025        );
5026
5027        // Test Deactivate Subcommand w/ absent fee payer and nonce authority
5028        let key2 = solana_pubkey::new_rand();
5029        let sig2 = Keypair::new().sign_message(&[0u8]);
5030        let signer2 = format!("{key2}={sig2}");
5031        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5032            "test",
5033            "deactivate-stake",
5034            &stake_account_string,
5035            "--blockhash",
5036            &blockhash_string,
5037            "--signer",
5038            &signer1,
5039            "--signer",
5040            &signer2,
5041            "--fee-payer",
5042            &key1.to_string(),
5043            "--nonce",
5044            &nonce_account.to_string(),
5045            "--nonce-authority",
5046            &key2.to_string(),
5047        ]);
5048        assert_eq!(
5049            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5050            CliCommandInfo {
5051                command: CliCommand::DeactivateStake {
5052                    stake_account_pubkey,
5053                    stake_authority: 0,
5054                    sign_only: false,
5055                    deactivate_delinquent: false,
5056                    dump_transaction_message: false,
5057                    blockhash_query: BlockhashQuery::Validated(
5058                        Source::NonceAccount(nonce_account),
5059                        blockhash
5060                    ),
5061                    nonce_account: Some(nonce_account),
5062                    nonce_authority: 2,
5063                    memo: None,
5064                    seed: None,
5065                    fee_payer: 1,
5066                    compute_unit_price: None,
5067                },
5068                signers: vec![
5069                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5070                    Box::new(Presigner::new(&key1, &sig1)),
5071                    Box::new(Presigner::new(&key2, &sig2)),
5072                ],
5073            }
5074        );
5075
5076        // Test Deactivate Subcommand w/ fee-payer
5077        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5078            "test",
5079            "deactivate-stake",
5080            &stake_account_string,
5081            "--fee-payer",
5082            &fee_payer_keypair_file,
5083        ]);
5084        assert_eq!(
5085            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5086            CliCommandInfo {
5087                command: CliCommand::DeactivateStake {
5088                    stake_account_pubkey,
5089                    stake_authority: 0,
5090                    sign_only: false,
5091                    deactivate_delinquent: false,
5092                    dump_transaction_message: false,
5093                    blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
5094                    nonce_account: None,
5095                    nonce_authority: 0,
5096                    memo: None,
5097                    seed: None,
5098                    fee_payer: 1,
5099                    compute_unit_price: None,
5100                },
5101                signers: vec![
5102                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5103                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
5104                ],
5105            }
5106        );
5107
5108        // Test SplitStake SubCommand
5109        let (keypair_file, mut tmp_file) = make_tmp_file();
5110        let stake_account_keypair = Keypair::new();
5111        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5112        let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
5113        let split_stake_account_keypair = Keypair::new();
5114        write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5115
5116        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5117            "test",
5118            "split-stake",
5119            &keypair_file,
5120            &split_stake_account_keypair_file,
5121            "50",
5122        ]);
5123        assert_eq!(
5124            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5125            CliCommandInfo {
5126                command: CliCommand::SplitStake {
5127                    stake_account_pubkey: stake_account_keypair.pubkey(),
5128                    stake_authority: 0,
5129                    sign_only: false,
5130                    dump_transaction_message: false,
5131                    blockhash_query: BlockhashQuery::default(),
5132                    nonce_account: None,
5133                    nonce_authority: 0,
5134                    memo: None,
5135                    split_stake_account: 1,
5136                    seed: None,
5137                    lamports: 50_000_000_000,
5138                    fee_payer: 0,
5139                    compute_unit_price: None,
5140                    rent_exempt_reserve: None,
5141                },
5142                signers: vec![
5143                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5144                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap())
5145                ],
5146            }
5147        );
5148
5149        // Split stake offline nonced submission
5150        let nonce_account = Pubkey::from([1u8; 32]);
5151        let nonce_account_string = nonce_account.to_string();
5152        let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
5153        let nonce_auth_pubkey = nonce_auth.pubkey();
5154        let nonce_auth_string = nonce_auth_pubkey.to_string();
5155        let nonce_sig = nonce_auth.sign_message(&[0u8]);
5156        let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
5157        let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
5158        let stake_auth_pubkey = stake_auth.pubkey();
5159        let stake_auth_string = stake_auth_pubkey.to_string();
5160        let stake_sig = stake_auth.sign_message(&[0u8]);
5161        let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
5162        let nonce_hash = Hash::new_from_array([4u8; 32]);
5163        let nonce_hash_string = nonce_hash.to_string();
5164
5165        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5166            "test",
5167            "split-stake",
5168            &keypair_file,
5169            &split_stake_account_keypair_file,
5170            "50",
5171            "--stake-authority",
5172            &stake_auth_string,
5173            "--blockhash",
5174            &nonce_hash_string,
5175            "--nonce",
5176            &nonce_account_string,
5177            "--nonce-authority",
5178            &nonce_auth_string,
5179            "--fee-payer",
5180            &nonce_auth_string, // Arbitrary choice of fee-payer
5181            "--signer",
5182            &nonce_signer,
5183            "--signer",
5184            &stake_signer,
5185        ]);
5186        assert_eq!(
5187            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5188            CliCommandInfo {
5189                command: CliCommand::SplitStake {
5190                    stake_account_pubkey: stake_account_keypair.pubkey(),
5191                    stake_authority: 0,
5192                    sign_only: false,
5193                    dump_transaction_message: false,
5194                    blockhash_query: BlockhashQuery::Validated(
5195                        Source::NonceAccount(nonce_account),
5196                        nonce_hash
5197                    ),
5198                    nonce_account: Some(nonce_account),
5199                    nonce_authority: 1,
5200                    memo: None,
5201                    split_stake_account: 2,
5202                    seed: None,
5203                    lamports: 50_000_000_000,
5204                    fee_payer: 1,
5205                    compute_unit_price: None,
5206                    rent_exempt_reserve: None,
5207                },
5208                signers: vec![
5209                    Box::new(Presigner::new(&stake_auth_pubkey, &stake_sig)),
5210                    Box::new(Presigner::new(&nonce_auth_pubkey, &nonce_sig)),
5211                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap()),
5212                ],
5213            }
5214        );
5215
5216        // Test MergeStake SubCommand
5217        let (keypair_file, mut tmp_file) = make_tmp_file();
5218        let stake_account_keypair = Keypair::new();
5219        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5220
5221        let source_stake_account_pubkey = solana_pubkey::new_rand();
5222        let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
5223            "test",
5224            "merge-stake",
5225            &keypair_file,
5226            &source_stake_account_pubkey.to_string(),
5227        ]);
5228        assert_eq!(
5229            parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
5230            CliCommandInfo {
5231                command: CliCommand::MergeStake {
5232                    stake_account_pubkey: stake_account_keypair.pubkey(),
5233                    source_stake_account_pubkey,
5234                    stake_authority: 0,
5235                    sign_only: false,
5236                    dump_transaction_message: false,
5237                    blockhash_query: BlockhashQuery::default(),
5238                    nonce_account: None,
5239                    nonce_authority: 0,
5240                    memo: None,
5241                    fee_payer: 0,
5242                    compute_unit_price: None,
5243                },
5244                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
5245            }
5246        );
5247    }
5248}