solana_cli/
stake.rs

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