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 stake account, in SOL. \
519                             Required for offline signing.",
520                        ),
521                ),
522        )
523        .subcommand(
524            SubCommand::with_name("merge-stake")
525                .about("Merges one stake account into another")
526                .arg(pubkey!(
527                    Arg::with_name("stake_account_pubkey")
528                        .index(1)
529                        .value_name("STAKE_ACCOUNT_ADDRESS")
530                        .required(true),
531                    "Stake account to merge into."
532                ))
533                .arg(pubkey!(
534                    Arg::with_name("source_stake_account_pubkey")
535                        .index(2)
536                        .value_name("SOURCE_STAKE_ACCOUNT_ADDRESS")
537                        .required(true),
538                    "Source stake account for the merge. If successful, this stake account will \
539                     no longer exist after the merge."
540                ))
541                .arg(stake_authority_arg())
542                .offline_args()
543                .nonce_args(false)
544                .arg(fee_payer_arg())
545                .arg(memo_arg())
546                .arg(compute_unit_price_arg()),
547        )
548        .subcommand(
549            SubCommand::with_name("withdraw-stake")
550                .about("Withdraw the unstaked SOL from the stake account")
551                .arg(pubkey!(
552                    Arg::with_name("stake_account_pubkey")
553                        .index(1)
554                        .value_name("STAKE_ACCOUNT_ADDRESS")
555                        .required(true),
556                    "Stake account from which to withdraw (or base of derived address if --seed \
557                     is used)."
558                ))
559                .arg(pubkey!(
560                    Arg::with_name("destination_account_pubkey")
561                        .index(2)
562                        .value_name("RECIPIENT_ADDRESS")
563                        .required(true),
564                    "Recipient of withdrawn stake."
565                ))
566                .arg(
567                    Arg::with_name("amount")
568                        .index(3)
569                        .value_name("AMOUNT")
570                        .takes_value(true)
571                        .validator(is_amount_or_all_or_available)
572                        .required(true)
573                        .help(
574                            "The amount to withdraw from the stake account, in SOL; accepts \
575                             keywords ALL or AVAILABLE",
576                        ),
577                )
578                .arg(
579                    Arg::with_name("seed")
580                        .long("seed")
581                        .value_name("STRING")
582                        .takes_value(true)
583                        .help(
584                            "Seed for address generation; if specified, the resulting account \
585                             will be at a derived address of STAKE_ACCOUNT_ADDRESS",
586                        ),
587                )
588                .arg(withdraw_authority_arg())
589                .offline_args()
590                .nonce_args(false)
591                .arg(fee_payer_arg())
592                .arg(custodian_arg())
593                .arg(memo_arg())
594                .arg(compute_unit_price_arg()),
595        )
596        .subcommand(
597            SubCommand::with_name("stake-set-lockup")
598                .about("Set Lockup for the stake account")
599                .arg(pubkey!(
600                    Arg::with_name("stake_account_pubkey")
601                        .index(1)
602                        .value_name("STAKE_ACCOUNT_ADDRESS")
603                        .required(true),
604                    "Stake account for which to set lockup parameters."
605                ))
606                .arg(
607                    Arg::with_name("lockup_epoch")
608                        .long("lockup-epoch")
609                        .value_name("NUMBER")
610                        .takes_value(true)
611                        .help(
612                            "The epoch height at which this account will be available for \
613                             withdrawal",
614                        ),
615                )
616                .arg(
617                    Arg::with_name("lockup_date")
618                        .long("lockup-date")
619                        .value_name("RFC3339 DATETIME")
620                        .validator(is_rfc3339_datetime)
621                        .takes_value(true)
622                        .help(
623                            "The date and time at which this account will be available for \
624                             withdrawal",
625                        ),
626                )
627                .arg(pubkey!(
628                    Arg::with_name("new_custodian")
629                        .long("new-custodian")
630                        .value_name("PUBKEY"),
631                    "New lockup custodian."
632                ))
633                .group(
634                    ArgGroup::with_name("lockup_details")
635                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
636                        .multiple(true)
637                        .required(true),
638                )
639                .arg(
640                    Arg::with_name("custodian")
641                        .long("custodian")
642                        .takes_value(true)
643                        .value_name("KEYPAIR")
644                        .validator(is_valid_signer)
645                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
646                )
647                .offline_args()
648                .nonce_args(false)
649                .arg(fee_payer_arg())
650                .arg(memo_arg())
651                .arg(compute_unit_price_arg()),
652        )
653        .subcommand(
654            SubCommand::with_name("stake-set-lockup-checked")
655                .about("Set Lockup for the stake account, checking the new authority as a signer")
656                .arg(pubkey!(
657                    Arg::with_name("stake_account_pubkey")
658                        .index(1)
659                        .value_name("STAKE_ACCOUNT_ADDRESS")
660                        .required(true),
661                    "Stake account for which to set lockup parameters."
662                ))
663                .arg(
664                    Arg::with_name("lockup_epoch")
665                        .long("lockup-epoch")
666                        .value_name("NUMBER")
667                        .takes_value(true)
668                        .help(
669                            "The epoch height at which this account will be available for \
670                             withdrawal",
671                        ),
672                )
673                .arg(
674                    Arg::with_name("lockup_date")
675                        .long("lockup-date")
676                        .value_name("RFC3339 DATETIME")
677                        .validator(is_rfc3339_datetime)
678                        .takes_value(true)
679                        .help(
680                            "The date and time at which this account will be available for \
681                             withdrawal",
682                        ),
683                )
684                .arg(
685                    Arg::with_name("new_custodian")
686                        .long("new-custodian")
687                        .value_name("KEYPAIR")
688                        .takes_value(true)
689                        .validator(is_valid_signer)
690                        .help("Keypair of a new lockup custodian"),
691                )
692                .group(
693                    ArgGroup::with_name("lockup_details")
694                        .args(&["lockup_epoch", "lockup_date", "new_custodian"])
695                        .multiple(true)
696                        .required(true),
697                )
698                .arg(
699                    Arg::with_name("custodian")
700                        .long("custodian")
701                        .takes_value(true)
702                        .value_name("KEYPAIR")
703                        .validator(is_valid_signer)
704                        .help("Keypair of the existing custodian [default: cli config pubkey]"),
705                )
706                .offline_args()
707                .nonce_args(false)
708                .arg(fee_payer_arg())
709                .arg(memo_arg())
710                .arg(compute_unit_price_arg()),
711        )
712        .subcommand(
713            SubCommand::with_name("stake-account")
714                .about("Show the contents of a stake account")
715                .alias("show-stake-account")
716                .arg(pubkey!(
717                    Arg::with_name("stake_account_pubkey")
718                        .index(1)
719                        .value_name("STAKE_ACCOUNT_ADDRESS")
720                        .required(true),
721                    "Stake account to display."
722                ))
723                .arg(
724                    Arg::with_name("lamports")
725                        .long("lamports")
726                        .takes_value(false)
727                        .help("Display balance in lamports instead of SOL"),
728                )
729                .arg(
730                    Arg::with_name("with_rewards")
731                        .long("with-rewards")
732                        .takes_value(false)
733                        .help("Display inflation rewards"),
734                )
735                .arg(
736                    Arg::with_name("csv")
737                        .long("csv")
738                        .takes_value(false)
739                        .help("Format stake rewards data in csv"),
740                )
741                .arg(
742                    Arg::with_name("starting_epoch")
743                        .long("starting-epoch")
744                        .takes_value(true)
745                        .value_name("NUM")
746                        .requires("with_rewards")
747                        .help("Start displaying from epoch NUM"),
748                )
749                .arg(
750                    Arg::with_name("num_rewards_epochs")
751                        .long("num-rewards-epochs")
752                        .takes_value(true)
753                        .value_name("NUM")
754                        .validator(|s| is_within_range(s, 1..=50))
755                        .default_value_if("with_rewards", None, "1")
756                        .requires("with_rewards")
757                        .help(
758                            "Display rewards for NUM recent epochs, max 10 [default: latest epoch \
759                             only]",
760                        ),
761                ),
762        )
763        .subcommand(
764            SubCommand::with_name("stake-history")
765                .about("Show the stake history")
766                .alias("show-stake-history")
767                .arg(
768                    Arg::with_name("lamports")
769                        .long("lamports")
770                        .takes_value(false)
771                        .help("Display balance in lamports instead of SOL"),
772                )
773                .arg(
774                    Arg::with_name("limit")
775                        .long("limit")
776                        .takes_value(true)
777                        .value_name("NUM")
778                        .default_value("10")
779                        .validator(|s| s.parse::<usize>().map(|_| ()).map_err(|e| e.to_string()))
780                        .help(
781                            "Display NUM recent epochs worth of stake history in text mode. 0 for \
782                             all",
783                        ),
784                ),
785        )
786        .subcommand(
787            SubCommand::with_name("stake-minimum-delegation")
788                .about("Get the stake minimum delegation amount")
789                .arg(
790                    Arg::with_name("lamports")
791                        .long("lamports")
792                        .takes_value(false)
793                        .help("Display minimum delegation in lamports instead of SOL"),
794                ),
795        )
796    }
797}
798
799pub fn parse_create_stake_account(
800    matches: &ArgMatches<'_>,
801    default_signer: &DefaultSigner,
802    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
803    checked: bool,
804) -> Result<CliCommandInfo, CliError> {
805    let seed = matches.value_of("seed").map(|s| s.to_string());
806    let epoch = value_of(matches, "lockup_epoch").unwrap_or(0);
807    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date").unwrap_or(0);
808    let custodian = pubkey_of_signer(matches, "custodian", wallet_manager)?.unwrap_or_default();
809    let staker = pubkey_of_signer(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
810
811    let (withdrawer_signer, withdrawer) = if checked {
812        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
813    } else {
814        (
815            None,
816            pubkey_of_signer(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?,
817        )
818    };
819
820    let amount = SpendAmount::new_from_matches(matches, "amount");
821    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
822    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
823    let blockhash_query = BlockhashQuery::new_from_matches(matches);
824    let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
825    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
826    let (nonce_authority, nonce_authority_pubkey) =
827        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
828    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
829    let (from, from_pubkey) = signer_of(matches, "from", wallet_manager)?;
830    let (stake_account, stake_account_pubkey) =
831        signer_of(matches, "stake_account", wallet_manager)?;
832
833    let mut bulk_signers = vec![fee_payer, from, stake_account];
834    if nonce_account.is_some() {
835        bulk_signers.push(nonce_authority);
836    }
837    if withdrawer_signer.is_some() {
838        bulk_signers.push(withdrawer_signer);
839    }
840    let signer_info =
841        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
842    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
843
844    Ok(CliCommandInfo {
845        command: CliCommand::CreateStakeAccount {
846            stake_account: signer_info.index_of(stake_account_pubkey).unwrap(),
847            seed,
848            staker,
849            withdrawer,
850            withdrawer_signer: if checked {
851                signer_info.index_of(withdrawer)
852            } else {
853                None
854            },
855            lockup: Lockup {
856                unix_timestamp,
857                epoch,
858                custodian,
859            },
860            amount,
861            sign_only,
862            dump_transaction_message,
863            blockhash_query,
864            nonce_account,
865            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
866            memo,
867            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
868            from: signer_info.index_of(from_pubkey).unwrap(),
869            compute_unit_price,
870        },
871        signers: signer_info.signers,
872    })
873}
874
875pub fn parse_stake_delegate_stake(
876    matches: &ArgMatches<'_>,
877    default_signer: &DefaultSigner,
878    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
879) -> Result<CliCommandInfo, CliError> {
880    let stake_account_pubkey =
881        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
882    let vote_account_pubkey =
883        pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
884    let force = matches.is_present("force");
885    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
886    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
887    let blockhash_query = BlockhashQuery::new_from_matches(matches);
888    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
889    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
890    let (stake_authority, stake_authority_pubkey) =
891        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
892    let (nonce_authority, nonce_authority_pubkey) =
893        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
894    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
895
896    let mut bulk_signers = vec![stake_authority, fee_payer];
897    if nonce_account.is_some() {
898        bulk_signers.push(nonce_authority);
899    }
900    let signer_info =
901        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
902    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
903
904    Ok(CliCommandInfo {
905        command: CliCommand::DelegateStake {
906            stake_account_pubkey,
907            vote_account_pubkey,
908            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
909            force,
910            sign_only,
911            dump_transaction_message,
912            blockhash_query,
913            nonce_account,
914            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
915            memo,
916            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
917            compute_unit_price,
918        },
919        signers: signer_info.signers,
920    })
921}
922
923pub fn parse_stake_authorize(
924    matches: &ArgMatches<'_>,
925    default_signer: &DefaultSigner,
926    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
927    checked: bool,
928) -> Result<CliCommandInfo, CliError> {
929    let stake_account_pubkey =
930        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
931
932    let mut new_authorizations = Vec::new();
933    let mut bulk_signers = Vec::new();
934
935    let (new_staker_signer, new_staker) = if checked {
936        signer_of(matches, "new_stake_authority", wallet_manager)?
937    } else {
938        (
939            None,
940            pubkey_of_signer(matches, "new_stake_authority", wallet_manager)?,
941        )
942    };
943
944    if let Some(new_authority_pubkey) = new_staker {
945        let (authority, authority_pubkey) = {
946            let (authority, authority_pubkey) =
947                signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
948            // Withdraw authority may also change the staker
949            if authority.is_none() {
950                signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?
951            } else {
952                (authority, authority_pubkey)
953            }
954        };
955        new_authorizations.push(StakeAuthorization {
956            authorization_type: StakeAuthorize::Staker,
957            new_authority_pubkey,
958            authority_pubkey,
959        });
960        bulk_signers.push(authority);
961        if new_staker.is_some() {
962            bulk_signers.push(new_staker_signer);
963        }
964    };
965
966    let (new_withdrawer_signer, new_withdrawer) = if checked {
967        signer_of(matches, "new_withdraw_authority", wallet_manager)?
968    } else {
969        (
970            None,
971            pubkey_of_signer(matches, "new_withdraw_authority", wallet_manager)?,
972        )
973    };
974
975    if let Some(new_authority_pubkey) = new_withdrawer {
976        let (authority, authority_pubkey) =
977            signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
978        new_authorizations.push(StakeAuthorization {
979            authorization_type: StakeAuthorize::Withdrawer,
980            new_authority_pubkey,
981            authority_pubkey,
982        });
983        bulk_signers.push(authority);
984        if new_withdrawer_signer.is_some() {
985            bulk_signers.push(new_withdrawer_signer);
986        }
987    };
988    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
989    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
990    let blockhash_query = BlockhashQuery::new_from_matches(matches);
991    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
992    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
993    let (nonce_authority, nonce_authority_pubkey) =
994        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
995    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
996    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
997    let no_wait = matches.is_present("no_wait");
998
999    bulk_signers.push(fee_payer);
1000    if nonce_account.is_some() {
1001        bulk_signers.push(nonce_authority);
1002    }
1003    if custodian.is_some() {
1004        bulk_signers.push(custodian);
1005    }
1006    let signer_info =
1007        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1008    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1009
1010    if new_authorizations.is_empty() {
1011        return Err(CliError::BadParameter(
1012            "New authorization list must include at least one authority".to_string(),
1013        ));
1014    }
1015    let new_authorizations = new_authorizations
1016        .into_iter()
1017        .map(
1018            |StakeAuthorization {
1019                 authorization_type,
1020                 new_authority_pubkey,
1021                 authority_pubkey,
1022             }| {
1023                StakeAuthorizationIndexed {
1024                    authorization_type,
1025                    new_authority_pubkey,
1026                    authority: signer_info.index_of(authority_pubkey).unwrap(),
1027                    new_authority_signer: signer_info.index_of(Some(new_authority_pubkey)),
1028                }
1029            },
1030        )
1031        .collect();
1032
1033    Ok(CliCommandInfo {
1034        command: CliCommand::StakeAuthorize {
1035            stake_account_pubkey,
1036            new_authorizations,
1037            sign_only,
1038            dump_transaction_message,
1039            blockhash_query,
1040            nonce_account,
1041            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1042            memo,
1043            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1044            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
1045            no_wait,
1046            compute_unit_price,
1047        },
1048        signers: signer_info.signers,
1049    })
1050}
1051
1052pub fn parse_split_stake(
1053    matches: &ArgMatches<'_>,
1054    default_signer: &DefaultSigner,
1055    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1056) -> Result<CliCommandInfo, CliError> {
1057    let stake_account_pubkey =
1058        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1059    let (split_stake_account, split_stake_account_pubkey) =
1060        signer_of(matches, "split_stake_account", wallet_manager)?;
1061    let lamports = lamports_of_sol(matches, "amount").unwrap();
1062    let seed = matches.value_of("seed").map(|s| s.to_string());
1063
1064    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1065    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1066    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1067    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1068    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1069    let (stake_authority, stake_authority_pubkey) =
1070        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1071    let (nonce_authority, nonce_authority_pubkey) =
1072        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1073    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1074
1075    let mut bulk_signers = vec![stake_authority, fee_payer, split_stake_account];
1076    if nonce_account.is_some() {
1077        bulk_signers.push(nonce_authority);
1078    }
1079    let signer_info =
1080        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1081    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1082    let rent_exempt_reserve = lamports_of_sol(matches, "rent_exempt_reserve_sol");
1083
1084    Ok(CliCommandInfo {
1085        command: CliCommand::SplitStake {
1086            stake_account_pubkey,
1087            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1088            sign_only,
1089            dump_transaction_message,
1090            blockhash_query,
1091            nonce_account,
1092            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1093            memo,
1094            split_stake_account: signer_info.index_of(split_stake_account_pubkey).unwrap(),
1095            seed,
1096            lamports,
1097            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1098            compute_unit_price,
1099            rent_exempt_reserve,
1100        },
1101        signers: signer_info.signers,
1102    })
1103}
1104
1105pub fn parse_merge_stake(
1106    matches: &ArgMatches<'_>,
1107    default_signer: &DefaultSigner,
1108    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1109) -> Result<CliCommandInfo, CliError> {
1110    let stake_account_pubkey =
1111        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1112
1113    let source_stake_account_pubkey = pubkey_of(matches, "source_stake_account_pubkey").unwrap();
1114
1115    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1116    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1117    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1118    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1119    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1120    let (stake_authority, stake_authority_pubkey) =
1121        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1122    let (nonce_authority, nonce_authority_pubkey) =
1123        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1124    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1125
1126    let mut bulk_signers = vec![stake_authority, fee_payer];
1127    if nonce_account.is_some() {
1128        bulk_signers.push(nonce_authority);
1129    }
1130    let signer_info =
1131        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1132    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1133
1134    Ok(CliCommandInfo {
1135        command: CliCommand::MergeStake {
1136            stake_account_pubkey,
1137            source_stake_account_pubkey,
1138            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1139            sign_only,
1140            dump_transaction_message,
1141            blockhash_query,
1142            nonce_account,
1143            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1144            memo,
1145            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1146            compute_unit_price,
1147        },
1148        signers: signer_info.signers,
1149    })
1150}
1151
1152pub fn parse_stake_deactivate_stake(
1153    matches: &ArgMatches<'_>,
1154    default_signer: &DefaultSigner,
1155    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1156) -> Result<CliCommandInfo, CliError> {
1157    let stake_account_pubkey =
1158        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1159    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1160    let deactivate_delinquent = matches.is_present("delinquent");
1161    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1162    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1163    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1164    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1165    let seed = value_t!(matches, "seed", String).ok();
1166
1167    let (stake_authority, stake_authority_pubkey) =
1168        signer_of(matches, STAKE_AUTHORITY_ARG.name, wallet_manager)?;
1169    let (nonce_authority, nonce_authority_pubkey) =
1170        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1171    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1172
1173    let mut bulk_signers = vec![stake_authority, fee_payer];
1174    if nonce_account.is_some() {
1175        bulk_signers.push(nonce_authority);
1176    }
1177    let signer_info =
1178        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1179    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1180
1181    Ok(CliCommandInfo {
1182        command: CliCommand::DeactivateStake {
1183            stake_account_pubkey,
1184            stake_authority: signer_info.index_of(stake_authority_pubkey).unwrap(),
1185            sign_only,
1186            deactivate_delinquent,
1187            dump_transaction_message,
1188            blockhash_query,
1189            nonce_account,
1190            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1191            memo,
1192            seed,
1193            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1194            compute_unit_price,
1195        },
1196        signers: signer_info.signers,
1197    })
1198}
1199
1200pub fn parse_stake_withdraw_stake(
1201    matches: &ArgMatches<'_>,
1202    default_signer: &DefaultSigner,
1203    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1204) -> Result<CliCommandInfo, CliError> {
1205    let stake_account_pubkey =
1206        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1207    let destination_account_pubkey =
1208        pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
1209    let amount = SpendAmount::new_from_matches(matches, "amount");
1210    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1211    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1212    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1213    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1214    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1215    let seed = value_t!(matches, "seed", String).ok();
1216    let (withdraw_authority, withdraw_authority_pubkey) =
1217        signer_of(matches, WITHDRAW_AUTHORITY_ARG.name, wallet_manager)?;
1218    let (nonce_authority, nonce_authority_pubkey) =
1219        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1220    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1221    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
1222
1223    let mut bulk_signers = vec![withdraw_authority, fee_payer];
1224    if nonce_account.is_some() {
1225        bulk_signers.push(nonce_authority);
1226    }
1227    if custodian.is_some() {
1228        bulk_signers.push(custodian);
1229    }
1230    let signer_info =
1231        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1232    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1233
1234    Ok(CliCommandInfo {
1235        command: CliCommand::WithdrawStake {
1236            stake_account_pubkey,
1237            destination_account_pubkey,
1238            amount,
1239            withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
1240            sign_only,
1241            dump_transaction_message,
1242            blockhash_query,
1243            nonce_account,
1244            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1245            memo,
1246            seed,
1247            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1248            custodian: custodian_pubkey.and_then(|_| signer_info.index_of(custodian_pubkey)),
1249            compute_unit_price,
1250        },
1251        signers: signer_info.signers,
1252    })
1253}
1254
1255pub fn parse_stake_set_lockup(
1256    matches: &ArgMatches<'_>,
1257    default_signer: &DefaultSigner,
1258    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1259    checked: bool,
1260) -> Result<CliCommandInfo, CliError> {
1261    let stake_account_pubkey =
1262        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1263    let epoch = value_of(matches, "lockup_epoch");
1264    let unix_timestamp = unix_timestamp_from_rfc3339_datetime(matches, "lockup_date");
1265
1266    let (new_custodian_signer, new_custodian) = if checked {
1267        signer_of(matches, "new_custodian", wallet_manager)?
1268    } else {
1269        (
1270            None,
1271            pubkey_of_signer(matches, "new_custodian", wallet_manager)?,
1272        )
1273    };
1274
1275    let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
1276    let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
1277    let blockhash_query = BlockhashQuery::new_from_matches(matches);
1278    let nonce_account = pubkey_of(matches, NONCE_ARG.name);
1279    let memo = matches.value_of(MEMO_ARG.name).map(String::from);
1280
1281    let (custodian, custodian_pubkey) = signer_of(matches, "custodian", wallet_manager)?;
1282    let (nonce_authority, nonce_authority_pubkey) =
1283        signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
1284    let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
1285
1286    let mut bulk_signers = vec![custodian, fee_payer];
1287    if nonce_account.is_some() {
1288        bulk_signers.push(nonce_authority);
1289    }
1290    if new_custodian_signer.is_some() {
1291        bulk_signers.push(new_custodian_signer);
1292    }
1293    let signer_info =
1294        default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
1295    let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
1296
1297    Ok(CliCommandInfo {
1298        command: CliCommand::StakeSetLockup {
1299            stake_account_pubkey,
1300            lockup: LockupArgs {
1301                custodian: new_custodian,
1302                epoch,
1303                unix_timestamp,
1304            },
1305            new_custodian_signer: if checked {
1306                signer_info.index_of(new_custodian)
1307            } else {
1308                None
1309            },
1310            custodian: signer_info.index_of(custodian_pubkey).unwrap(),
1311            sign_only,
1312            dump_transaction_message,
1313            blockhash_query,
1314            nonce_account,
1315            nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
1316            memo,
1317            fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
1318            compute_unit_price,
1319        },
1320        signers: signer_info.signers,
1321    })
1322}
1323
1324pub fn parse_show_stake_account(
1325    matches: &ArgMatches<'_>,
1326    wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
1327) -> Result<CliCommandInfo, CliError> {
1328    let stake_account_pubkey =
1329        pubkey_of_signer(matches, "stake_account_pubkey", wallet_manager)?.unwrap();
1330    let use_lamports_unit = matches.is_present("lamports");
1331    let use_csv = matches.is_present("csv");
1332    let with_rewards = if matches.is_present("with_rewards") {
1333        Some(value_of(matches, "num_rewards_epochs").unwrap())
1334    } else {
1335        None
1336    };
1337    let starting_epoch = value_of(matches, "starting_epoch");
1338    Ok(CliCommandInfo::without_signers(
1339        CliCommand::ShowStakeAccount {
1340            pubkey: stake_account_pubkey,
1341            use_lamports_unit,
1342            with_rewards,
1343            use_csv,
1344            starting_epoch,
1345        },
1346    ))
1347}
1348
1349pub fn parse_show_stake_history(matches: &ArgMatches<'_>) -> Result<CliCommandInfo, CliError> {
1350    let use_lamports_unit = matches.is_present("lamports");
1351    let limit_results = value_of(matches, "limit").unwrap();
1352    Ok(CliCommandInfo::without_signers(
1353        CliCommand::ShowStakeHistory {
1354            use_lamports_unit,
1355            limit_results,
1356        },
1357    ))
1358}
1359
1360pub fn parse_stake_minimum_delegation(
1361    matches: &ArgMatches<'_>,
1362) -> Result<CliCommandInfo, CliError> {
1363    let use_lamports_unit = matches.is_present("lamports");
1364    Ok(CliCommandInfo::without_signers(
1365        CliCommand::StakeMinimumDelegation { use_lamports_unit },
1366    ))
1367}
1368
1369#[allow(clippy::too_many_arguments)]
1370pub 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, provided: \
2013                 {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 \
2027                             split stake"
2028                        )))
2029                    } else {
2030                        // if `stake_account`'s owner is the system_program and its data is
2031                        // empty, `stake_account` is allowed to receive the stake split
2032                        Ok(account.lamports)
2033                    }
2034                }
2035                _ => Err(CliError::BadParameter(format!(
2036                    "Account {split_stake_account_address} already exists and cannot be used to \
2037                     split stake"
2038                ))),
2039            }
2040        };
2041        let current_balance =
2042            if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address) {
2043                check_stake_account(stake_account)?
2044            } else {
2045                0
2046            };
2047
2048        let rent_exempt_reserve =
2049            rpc_client.get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())?;
2050
2051        rent_exempt_reserve.saturating_sub(current_balance)
2052    };
2053
2054    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2055
2056    let mut ixs = vec![];
2057    if rent_exempt_reserve > 0 {
2058        ixs.push(system_instruction::transfer(
2059            &fee_payer.pubkey(),
2060            &split_stake_account_address,
2061            rent_exempt_reserve,
2062        ));
2063    }
2064    let compute_unit_limit = match blockhash_query {
2065        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
2066        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
2067    };
2068    if let Some(seed) = split_stake_account_seed {
2069        ixs.append(
2070            &mut stake_instruction::split_with_seed(
2071                stake_account_pubkey,
2072                &stake_authority.pubkey(),
2073                lamports,
2074                &split_stake_account_address,
2075                &split_stake_account.pubkey(),
2076                seed,
2077            )
2078            .with_memo(memo)
2079            .with_compute_unit_config(&ComputeUnitConfig {
2080                compute_unit_price,
2081                compute_unit_limit,
2082            }),
2083        )
2084    } else {
2085        ixs.append(
2086            &mut stake_instruction::split(
2087                stake_account_pubkey,
2088                &stake_authority.pubkey(),
2089                lamports,
2090                &split_stake_account_address,
2091            )
2092            .with_memo(memo)
2093            .with_compute_unit_config(&ComputeUnitConfig {
2094                compute_unit_price,
2095                compute_unit_limit,
2096            }),
2097        )
2098    };
2099
2100    let nonce_authority = config.signers[nonce_authority];
2101
2102    let mut message = if let Some(nonce_account) = &nonce_account {
2103        Message::new_with_nonce(
2104            ixs,
2105            Some(&fee_payer.pubkey()),
2106            nonce_account,
2107            &nonce_authority.pubkey(),
2108        )
2109    } else {
2110        Message::new(&ixs, Some(&fee_payer.pubkey()))
2111    };
2112    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
2113    let mut tx = Transaction::new_unsigned(message);
2114
2115    if sign_only {
2116        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2117        return_signers_with_config(
2118            &tx,
2119            &config.output_format,
2120            &ReturnSignersConfig {
2121                dump_transaction_message,
2122            },
2123        )
2124    } else {
2125        tx.try_sign(&config.signers, recent_blockhash)?;
2126        if let Some(nonce_account) = &nonce_account {
2127            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
2128                rpc_client,
2129                nonce_account,
2130                config.commitment,
2131            )?;
2132            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2133        }
2134        check_account_for_fee_with_commitment(
2135            rpc_client,
2136            &tx.message.account_keys[0],
2137            &tx.message,
2138            config.commitment,
2139        )?;
2140        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
2141            &tx,
2142            config.commitment,
2143            config.send_transaction_config,
2144        );
2145        log_instruction_custom_error::<StakeError>(result, config)
2146    }
2147}
2148
2149#[allow(clippy::too_many_arguments)]
2150pub fn process_merge_stake(
2151    rpc_client: &RpcClient,
2152    config: &CliConfig,
2153    stake_account_pubkey: &Pubkey,
2154    source_stake_account_pubkey: &Pubkey,
2155    stake_authority: SignerIndex,
2156    sign_only: bool,
2157    dump_transaction_message: bool,
2158    blockhash_query: &BlockhashQuery,
2159    nonce_account: Option<Pubkey>,
2160    nonce_authority: SignerIndex,
2161    memo: Option<&String>,
2162    fee_payer: SignerIndex,
2163    compute_unit_price: Option<u64>,
2164) -> ProcessResult {
2165    let fee_payer = config.signers[fee_payer];
2166
2167    check_unique_pubkeys(
2168        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2169        (stake_account_pubkey, "stake_account".to_string()),
2170    )?;
2171    check_unique_pubkeys(
2172        (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2173        (
2174            source_stake_account_pubkey,
2175            "source_stake_account".to_string(),
2176        ),
2177    )?;
2178    check_unique_pubkeys(
2179        (stake_account_pubkey, "stake_account".to_string()),
2180        (
2181            source_stake_account_pubkey,
2182            "source_stake_account".to_string(),
2183        ),
2184    )?;
2185
2186    let stake_authority = config.signers[stake_authority];
2187
2188    if !sign_only {
2189        for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] {
2190            if let Ok(stake_account) = rpc_client.get_account(stake_account_address) {
2191                if stake_account.owner != stake::program::id() {
2192                    return Err(CliError::BadParameter(format!(
2193                        "Account {stake_account_address} is not a stake account"
2194                    ))
2195                    .into());
2196                }
2197            }
2198        }
2199    }
2200
2201    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2202
2203    let compute_unit_limit = match blockhash_query {
2204        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
2205        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
2206    };
2207    let ixs = stake_instruction::merge(
2208        stake_account_pubkey,
2209        source_stake_account_pubkey,
2210        &stake_authority.pubkey(),
2211    )
2212    .with_memo(memo)
2213    .with_compute_unit_config(&ComputeUnitConfig {
2214        compute_unit_price,
2215        compute_unit_limit,
2216    });
2217
2218    let nonce_authority = config.signers[nonce_authority];
2219
2220    let mut message = if let Some(nonce_account) = &nonce_account {
2221        Message::new_with_nonce(
2222            ixs,
2223            Some(&fee_payer.pubkey()),
2224            nonce_account,
2225            &nonce_authority.pubkey(),
2226        )
2227    } else {
2228        Message::new(&ixs, Some(&fee_payer.pubkey()))
2229    };
2230    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
2231    let mut tx = Transaction::new_unsigned(message);
2232
2233    if sign_only {
2234        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2235        return_signers_with_config(
2236            &tx,
2237            &config.output_format,
2238            &ReturnSignersConfig {
2239                dump_transaction_message,
2240            },
2241        )
2242    } else {
2243        tx.try_sign(&config.signers, recent_blockhash)?;
2244        if let Some(nonce_account) = &nonce_account {
2245            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
2246                rpc_client,
2247                nonce_account,
2248                config.commitment,
2249            )?;
2250            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2251        }
2252        check_account_for_fee_with_commitment(
2253            rpc_client,
2254            &tx.message.account_keys[0],
2255            &tx.message,
2256            config.commitment,
2257        )?;
2258        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
2259            &tx,
2260            config.commitment,
2261            config.send_transaction_config,
2262        );
2263        log_instruction_custom_error::<StakeError>(result, config)
2264    }
2265}
2266
2267#[allow(clippy::too_many_arguments)]
2268pub fn process_stake_set_lockup(
2269    rpc_client: &RpcClient,
2270    config: &CliConfig,
2271    stake_account_pubkey: &Pubkey,
2272    lockup: &LockupArgs,
2273    new_custodian_signer: Option<SignerIndex>,
2274    custodian: SignerIndex,
2275    sign_only: bool,
2276    dump_transaction_message: bool,
2277    blockhash_query: &BlockhashQuery,
2278    nonce_account: Option<Pubkey>,
2279    nonce_authority: SignerIndex,
2280    memo: Option<&String>,
2281    fee_payer: SignerIndex,
2282    compute_unit_price: Option<u64>,
2283) -> ProcessResult {
2284    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2285    let custodian = config.signers[custodian];
2286
2287    let compute_unit_limit = match blockhash_query {
2288        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
2289        BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
2290    };
2291    let ixs = vec![if new_custodian_signer.is_some() {
2292        stake_instruction::set_lockup_checked(stake_account_pubkey, lockup, &custodian.pubkey())
2293    } else {
2294        stake_instruction::set_lockup(stake_account_pubkey, lockup, &custodian.pubkey())
2295    }]
2296    .with_memo(memo)
2297    .with_compute_unit_config(&ComputeUnitConfig {
2298        compute_unit_price,
2299        compute_unit_limit,
2300    });
2301    let nonce_authority = config.signers[nonce_authority];
2302    let fee_payer = config.signers[fee_payer];
2303
2304    if !sign_only {
2305        let state = get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment)?;
2306        let lockup = match state {
2307            StakeStateV2::Stake(Meta { lockup, .. }, ..) => Some(lockup),
2308            StakeStateV2::Initialized(Meta { lockup, .. }) => Some(lockup),
2309            _ => None,
2310        };
2311        if let Some(lockup) = lockup {
2312            if lockup.custodian != Pubkey::default() {
2313                check_current_authority(&[lockup.custodian], &custodian.pubkey())?;
2314            }
2315        } else {
2316            return Err(CliError::RpcRequestError(format!(
2317                "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
2318            ))
2319            .into());
2320        }
2321    }
2322
2323    let mut message = if let Some(nonce_account) = &nonce_account {
2324        Message::new_with_nonce(
2325            ixs,
2326            Some(&fee_payer.pubkey()),
2327            nonce_account,
2328            &nonce_authority.pubkey(),
2329        )
2330    } else {
2331        Message::new(&ixs, Some(&fee_payer.pubkey()))
2332    };
2333    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
2334    let mut tx = Transaction::new_unsigned(message);
2335
2336    if sign_only {
2337        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2338        return_signers_with_config(
2339            &tx,
2340            &config.output_format,
2341            &ReturnSignersConfig {
2342                dump_transaction_message,
2343            },
2344        )
2345    } else {
2346        tx.try_sign(&config.signers, recent_blockhash)?;
2347        if let Some(nonce_account) = &nonce_account {
2348            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
2349                rpc_client,
2350                nonce_account,
2351                config.commitment,
2352            )?;
2353            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2354        }
2355        check_account_for_fee_with_commitment(
2356            rpc_client,
2357            &tx.message.account_keys[0],
2358            &tx.message,
2359            config.commitment,
2360        )?;
2361        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
2362            &tx,
2363            config.commitment,
2364            config.send_transaction_config,
2365        );
2366        log_instruction_custom_error::<StakeError>(result, config)
2367    }
2368}
2369
2370fn u64_some_if_not_zero(n: u64) -> Option<u64> {
2371    if n > 0 {
2372        Some(n)
2373    } else {
2374        None
2375    }
2376}
2377
2378pub fn build_stake_state(
2379    account_balance: u64,
2380    stake_state: &StakeStateV2,
2381    use_lamports_unit: bool,
2382    stake_history: &StakeHistory,
2383    clock: &Clock,
2384    new_rate_activation_epoch: Option<Epoch>,
2385    use_csv: bool,
2386) -> CliStakeState {
2387    match stake_state {
2388        StakeStateV2::Stake(
2389            Meta {
2390                rent_exempt_reserve,
2391                authorized,
2392                lockup,
2393            },
2394            stake,
2395            _,
2396        ) => {
2397            let current_epoch = clock.epoch;
2398            let StakeActivationStatus {
2399                effective,
2400                activating,
2401                deactivating,
2402            } = stake.delegation.stake_activating_and_deactivating(
2403                current_epoch,
2404                stake_history,
2405                new_rate_activation_epoch,
2406            );
2407            let lockup = if lockup.is_in_force(clock, None) {
2408                Some(lockup.into())
2409            } else {
2410                None
2411            };
2412            CliStakeState {
2413                stake_type: CliStakeType::Stake,
2414                account_balance,
2415                credits_observed: Some(stake.credits_observed),
2416                delegated_stake: Some(stake.delegation.stake),
2417                delegated_vote_account_address: if stake.delegation.voter_pubkey
2418                    != Pubkey::default()
2419                {
2420                    Some(stake.delegation.voter_pubkey.to_string())
2421                } else {
2422                    None
2423                },
2424                activation_epoch: Some(if stake.delegation.activation_epoch < u64::MAX {
2425                    stake.delegation.activation_epoch
2426                } else {
2427                    0
2428                }),
2429                deactivation_epoch: if stake.delegation.deactivation_epoch < u64::MAX {
2430                    Some(stake.delegation.deactivation_epoch)
2431                } else {
2432                    None
2433                },
2434                authorized: Some(authorized.into()),
2435                lockup,
2436                use_lamports_unit,
2437                current_epoch,
2438                rent_exempt_reserve: Some(*rent_exempt_reserve),
2439                active_stake: u64_some_if_not_zero(effective),
2440                activating_stake: u64_some_if_not_zero(activating),
2441                deactivating_stake: u64_some_if_not_zero(deactivating),
2442                use_csv,
2443                ..CliStakeState::default()
2444            }
2445        }
2446        StakeStateV2::RewardsPool => CliStakeState {
2447            stake_type: CliStakeType::RewardsPool,
2448            account_balance,
2449            ..CliStakeState::default()
2450        },
2451        StakeStateV2::Uninitialized => CliStakeState {
2452            account_balance,
2453            ..CliStakeState::default()
2454        },
2455        StakeStateV2::Initialized(Meta {
2456            rent_exempt_reserve,
2457            authorized,
2458            lockup,
2459        }) => {
2460            let lockup = if lockup.is_in_force(clock, None) {
2461                Some(lockup.into())
2462            } else {
2463                None
2464            };
2465            CliStakeState {
2466                stake_type: CliStakeType::Initialized,
2467                account_balance,
2468                credits_observed: Some(0),
2469                authorized: Some(authorized.into()),
2470                lockup,
2471                use_lamports_unit,
2472                rent_exempt_reserve: Some(*rent_exempt_reserve),
2473                ..CliStakeState::default()
2474            }
2475        }
2476    }
2477}
2478
2479fn get_stake_account_state(
2480    rpc_client: &RpcClient,
2481    stake_account_pubkey: &Pubkey,
2482    commitment_config: CommitmentConfig,
2483) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
2484    let stake_account = rpc_client
2485        .get_account_with_commitment(stake_account_pubkey, commitment_config)?
2486        .value
2487        .ok_or_else(|| {
2488            CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
2489        })?;
2490    if stake_account.owner != stake::program::id() {
2491        return Err(CliError::RpcRequestError(format!(
2492            "{stake_account_pubkey:?} is not a stake account",
2493        ))
2494        .into());
2495    }
2496    stake_account.state().map_err(|err| {
2497        CliError::RpcRequestError(format!(
2498            "Account data could not be deserialized to stake state: {err}"
2499        ))
2500        .into()
2501    })
2502}
2503
2504pub(crate) fn check_current_authority(
2505    permitted_authorities: &[Pubkey],
2506    provided_current_authority: &Pubkey,
2507) -> Result<(), CliError> {
2508    if !permitted_authorities.contains(provided_current_authority) {
2509        Err(CliError::RpcRequestError(format!(
2510            "Invalid authority provided: {provided_current_authority:?}, expected \
2511             {permitted_authorities:?}"
2512        )))
2513    } else {
2514        Ok(())
2515    }
2516}
2517
2518pub fn get_epoch_boundary_timestamps(
2519    rpc_client: &RpcClient,
2520    reward: &RpcInflationReward,
2521    epoch_schedule: &EpochSchedule,
2522) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
2523    let epoch_end_time = rpc_client.get_block_time(reward.effective_slot)?;
2524    let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
2525    let epoch_start_time = loop {
2526        if epoch_start_slot >= reward.effective_slot {
2527            return Err("epoch_start_time not found".to_string().into());
2528        }
2529        match rpc_client.get_block_time(epoch_start_slot) {
2530            Ok(block_time) => {
2531                break block_time;
2532            }
2533            Err(_) => {
2534                // TODO This is wrong.  We should not just increase the slot index if the RPC
2535                // request failed.  It could have failed for a number of reasons, including, for
2536                // example a network failure.
2537                epoch_start_slot = epoch_start_slot
2538                    .checked_add(1)
2539                    .ok_or("Reached last slot that fits into u64")?;
2540            }
2541        }
2542    };
2543    Ok((epoch_start_time, epoch_end_time))
2544}
2545
2546pub fn make_cli_reward(
2547    reward: &RpcInflationReward,
2548    block_time: UnixTimestamp,
2549    epoch_start_time: UnixTimestamp,
2550    epoch_end_time: UnixTimestamp,
2551) -> Option<CliEpochReward> {
2552    let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
2553    if reward.post_balance > reward.amount {
2554        let rate_change =
2555            reward.amount as f64 / (reward.post_balance.saturating_sub(reward.amount)) as f64;
2556
2557        let wallclock_epochs_per_year =
2558            (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
2559        let apr = rate_change * wallclock_epochs_per_year;
2560
2561        Some(CliEpochReward {
2562            epoch: reward.epoch,
2563            effective_slot: reward.effective_slot,
2564            amount: reward.amount,
2565            post_balance: reward.post_balance,
2566            percent_change: rate_change * 100.0,
2567            apr: Some(apr * 100.0),
2568            commission: reward.commission,
2569            block_time,
2570        })
2571    } else {
2572        None
2573    }
2574}
2575
2576pub(crate) fn fetch_epoch_rewards(
2577    rpc_client: &RpcClient,
2578    address: &Pubkey,
2579    mut num_epochs: usize,
2580    starting_epoch: Option<u64>,
2581) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
2582    let mut all_epoch_rewards = vec![];
2583    let epoch_schedule = rpc_client.get_epoch_schedule()?;
2584    let mut rewards_epoch = if let Some(epoch) = starting_epoch {
2585        epoch
2586    } else {
2587        rpc_client
2588            .get_epoch_info()?
2589            .epoch
2590            .saturating_sub(num_epochs as u64)
2591    };
2592
2593    let mut process_reward =
2594        |reward: &Option<RpcInflationReward>| -> Result<(), Box<dyn std::error::Error>> {
2595            if let Some(reward) = reward {
2596                let (epoch_start_time, epoch_end_time) =
2597                    get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule)?;
2598                let block_time = rpc_client.get_block_time(reward.effective_slot)?;
2599                if let Some(cli_reward) =
2600                    make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
2601                {
2602                    all_epoch_rewards.push(cli_reward);
2603                }
2604            }
2605            Ok(())
2606        };
2607
2608    while num_epochs > 0 {
2609        if let Ok(rewards) = rpc_client.get_inflation_reward(&[*address], Some(rewards_epoch)) {
2610            process_reward(&rewards[0])?;
2611        } else {
2612            eprintln!("Rewards not available for epoch {rewards_epoch}");
2613        }
2614        num_epochs = num_epochs.saturating_sub(1);
2615        rewards_epoch = rewards_epoch.saturating_add(1);
2616    }
2617
2618    Ok(all_epoch_rewards)
2619}
2620
2621pub fn process_show_stake_account(
2622    rpc_client: &RpcClient,
2623    config: &CliConfig,
2624    stake_account_address: &Pubkey,
2625    use_lamports_unit: bool,
2626    with_rewards: Option<usize>,
2627    use_csv: bool,
2628    starting_epoch: Option<u64>,
2629) -> ProcessResult {
2630    let stake_account = rpc_client.get_account(stake_account_address)?;
2631    let state = get_account_stake_state(
2632        rpc_client,
2633        stake_account_address,
2634        stake_account,
2635        use_lamports_unit,
2636        with_rewards,
2637        use_csv,
2638        starting_epoch,
2639    )?;
2640    Ok(config.output_format.formatted_string(&state))
2641}
2642
2643pub fn get_account_stake_state(
2644    rpc_client: &RpcClient,
2645    stake_account_address: &Pubkey,
2646    stake_account: solana_account::Account,
2647    use_lamports_unit: bool,
2648    with_rewards: Option<usize>,
2649    use_csv: bool,
2650    starting_epoch: Option<u64>,
2651) -> Result<CliStakeState, CliError> {
2652    if stake_account.owner != stake::program::id() {
2653        return Err(CliError::RpcRequestError(format!(
2654            "{stake_account_address:?} is not a stake account",
2655        )));
2656    }
2657    match stake_account.state() {
2658        Ok(stake_state) => {
2659            let stake_history_account = rpc_client.get_account(&stake_history::id())?;
2660            let stake_history = from_account(&stake_history_account).ok_or_else(|| {
2661                CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2662            })?;
2663            let clock_account = rpc_client.get_account(&clock::id())?;
2664            let clock: Clock = from_account(&clock_account).ok_or_else(|| {
2665                CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
2666            })?;
2667            let new_rate_activation_epoch = get_feature_activation_epoch(
2668                rpc_client,
2669                &agave_feature_set::reduce_stake_warmup_cooldown::id(),
2670            )?;
2671
2672            let mut state = build_stake_state(
2673                stake_account.lamports,
2674                &stake_state,
2675                use_lamports_unit,
2676                &stake_history,
2677                &clock,
2678                new_rate_activation_epoch,
2679                use_csv,
2680            );
2681
2682            if state.stake_type == CliStakeType::Stake && state.activation_epoch.is_some() {
2683                let epoch_rewards = with_rewards.and_then(|num_epochs| {
2684                    match fetch_epoch_rewards(
2685                        rpc_client,
2686                        stake_account_address,
2687                        num_epochs,
2688                        starting_epoch,
2689                    ) {
2690                        Ok(rewards) => Some(rewards),
2691                        Err(error) => {
2692                            eprintln!("Failed to fetch epoch rewards: {error:?}");
2693                            None
2694                        }
2695                    }
2696                });
2697                state.epoch_rewards = epoch_rewards;
2698            }
2699            Ok(state)
2700        }
2701        Err(err) => Err(CliError::RpcRequestError(format!(
2702            "Account data could not be deserialized to stake state: {err}"
2703        ))),
2704    }
2705}
2706
2707pub fn process_show_stake_history(
2708    rpc_client: &RpcClient,
2709    config: &CliConfig,
2710    use_lamports_unit: bool,
2711    limit_results: usize,
2712) -> ProcessResult {
2713    let stake_history_account = rpc_client.get_account(&stake_history::id())?;
2714    let stake_history =
2715        from_account::<StakeHistory, _>(&stake_history_account).ok_or_else(|| {
2716            CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2717        })?;
2718
2719    let limit_results = match config.output_format {
2720        OutputFormat::Json | OutputFormat::JsonCompact => usize::MAX,
2721        _ => {
2722            if limit_results == 0 {
2723                usize::MAX
2724            } else {
2725                limit_results
2726            }
2727        }
2728    };
2729    let mut entries: Vec<CliStakeHistoryEntry> = vec![];
2730    for entry in stake_history.deref().iter().take(limit_results) {
2731        entries.push(entry.into());
2732    }
2733    let stake_history_output = CliStakeHistory {
2734        entries,
2735        use_lamports_unit,
2736    };
2737    Ok(config.output_format.formatted_string(&stake_history_output))
2738}
2739
2740#[allow(clippy::too_many_arguments)]
2741pub fn process_delegate_stake(
2742    rpc_client: &RpcClient,
2743    config: &CliConfig,
2744    stake_account_pubkey: &Pubkey,
2745    vote_account_pubkey: &Pubkey,
2746    stake_authority: SignerIndex,
2747    force: bool,
2748    sign_only: bool,
2749    dump_transaction_message: bool,
2750    blockhash_query: &BlockhashQuery,
2751    nonce_account: Option<Pubkey>,
2752    nonce_authority: SignerIndex,
2753    memo: Option<&String>,
2754    fee_payer: SignerIndex,
2755    compute_unit_price: Option<u64>,
2756) -> ProcessResult {
2757    check_unique_pubkeys(
2758        (&config.signers[0].pubkey(), "cli keypair".to_string()),
2759        (stake_account_pubkey, "stake_account_pubkey".to_string()),
2760    )?;
2761    let stake_authority = config.signers[stake_authority];
2762
2763    if !sign_only {
2764        // Sanity check the vote account to ensure it is attached to a validator that has recently
2765        // voted at the tip of the ledger
2766        let get_vote_accounts_config = RpcGetVoteAccountsConfig {
2767            vote_pubkey: Some(vote_account_pubkey.to_string()),
2768            keep_unstaked_delinquents: Some(true),
2769            commitment: Some(rpc_client.commitment()),
2770            ..RpcGetVoteAccountsConfig::default()
2771        };
2772        let RpcVoteAccountStatus {
2773            current,
2774            delinquent,
2775        } = rpc_client.get_vote_accounts_with_config(get_vote_accounts_config)?;
2776        // filter should return at most one result
2777        let rpc_vote_account =
2778            current
2779                .first()
2780                .or_else(|| delinquent.first())
2781                .ok_or(CliError::RpcRequestError(format!(
2782                    "Vote account not found: {vote_account_pubkey}"
2783                )))?;
2784
2785        let activated_stake = rpc_vote_account.activated_stake;
2786        let root_slot = rpc_vote_account.root_slot;
2787        let min_root_slot = rpc_client
2788            .get_slot()
2789            .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
2790        let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
2791            Ok(())
2792        } else if root_slot == 0 {
2793            Err(CliError::BadParameter(
2794                "Unable to delegate. Vote account has no root slot".to_string(),
2795            ))
2796        } else {
2797            Err(CliError::DynamicProgramError(format!(
2798                "Unable to delegate.  Vote account appears delinquent because its current root \
2799                 slot, {root_slot}, is less than {min_root_slot}"
2800            )))
2801        };
2802
2803        if let Err(err) = &sanity_check_result {
2804            if !force {
2805                sanity_check_result?;
2806            } else {
2807                println!("--force supplied, ignoring: {err}");
2808            }
2809        }
2810    }
2811
2812    let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
2813
2814    // DelegateStake parses a VoteState, which may change between simulation and execution
2815    let compute_unit_limit = match blockhash_query {
2816        BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
2817        BlockhashQuery::All(_) => ComputeUnitLimit::SimulatedWithExtraPercentage(5),
2818    };
2819    let ixs = vec![stake_instruction::delegate_stake(
2820        stake_account_pubkey,
2821        &stake_authority.pubkey(),
2822        vote_account_pubkey,
2823    )]
2824    .with_memo(memo)
2825    .with_compute_unit_config(&ComputeUnitConfig {
2826        compute_unit_price,
2827        compute_unit_limit,
2828    });
2829
2830    let nonce_authority = config.signers[nonce_authority];
2831    let fee_payer = config.signers[fee_payer];
2832
2833    let mut message = if let Some(nonce_account) = &nonce_account {
2834        Message::new_with_nonce(
2835            ixs,
2836            Some(&fee_payer.pubkey()),
2837            nonce_account,
2838            &nonce_authority.pubkey(),
2839        )
2840    } else {
2841        Message::new(&ixs, Some(&fee_payer.pubkey()))
2842    };
2843    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
2844    let mut tx = Transaction::new_unsigned(message);
2845
2846    if sign_only {
2847        tx.try_partial_sign(&config.signers, recent_blockhash)?;
2848        return_signers_with_config(
2849            &tx,
2850            &config.output_format,
2851            &ReturnSignersConfig {
2852                dump_transaction_message,
2853            },
2854        )
2855    } else {
2856        tx.try_sign(&config.signers, recent_blockhash)?;
2857        if let Some(nonce_account) = &nonce_account {
2858            let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
2859                rpc_client,
2860                nonce_account,
2861                config.commitment,
2862            )?;
2863            check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2864        }
2865        check_account_for_fee_with_commitment(
2866            rpc_client,
2867            &tx.message.account_keys[0],
2868            &tx.message,
2869            config.commitment,
2870        )?;
2871        let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
2872            &tx,
2873            config.commitment,
2874            config.send_transaction_config,
2875        );
2876        log_instruction_custom_error::<StakeError>(result, config)
2877    }
2878}
2879
2880pub fn process_stake_minimum_delegation(
2881    rpc_client: &RpcClient,
2882    config: &CliConfig,
2883    use_lamports_unit: bool,
2884) -> ProcessResult {
2885    let stake_minimum_delegation =
2886        rpc_client.get_stake_minimum_delegation_with_commitment(config.commitment)?;
2887
2888    let stake_minimum_delegation_output = CliBalance {
2889        lamports: stake_minimum_delegation,
2890        config: BuildBalanceMessageConfig {
2891            use_lamports_unit,
2892            show_unit: true,
2893            trim_trailing_zeros: true,
2894        },
2895    };
2896
2897    Ok(config
2898        .output_format
2899        .formatted_string(&stake_minimum_delegation_output))
2900}
2901
2902#[cfg(test)]
2903mod tests {
2904    use {
2905        super::*,
2906        crate::{clap_app::get_clap_app, cli::parse_command},
2907        solana_hash::Hash,
2908        solana_keypair::{keypair_from_seed, read_keypair_file, write_keypair, Keypair},
2909        solana_presigner::Presigner,
2910        solana_rpc_client_nonce_utils::blockhash_query,
2911        solana_signer::Signer,
2912        tempfile::NamedTempFile,
2913    };
2914
2915    fn make_tmp_file() -> (String, NamedTempFile) {
2916        let tmp_file = NamedTempFile::new().unwrap();
2917        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
2918    }
2919
2920    #[test]
2921    #[allow(clippy::cognitive_complexity)]
2922    fn test_parse_command() {
2923        let test_commands = get_clap_app("test", "desc", "version");
2924        let default_keypair = Keypair::new();
2925        let (default_keypair_file, mut tmp_file) = make_tmp_file();
2926        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
2927        let default_signer = DefaultSigner::new("", &default_keypair_file);
2928        let (keypair_file, mut tmp_file) = make_tmp_file();
2929        let stake_account_keypair = Keypair::new();
2930        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
2931        let stake_account_pubkey = stake_account_keypair.pubkey();
2932        let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
2933        let stake_authority_keypair = Keypair::new();
2934        write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
2935        let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
2936        let custodian_keypair = Keypair::new();
2937        write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
2938
2939        // stake-authorize subcommand
2940        let stake_account_string = stake_account_pubkey.to_string();
2941        let new_stake_authority = Pubkey::from([1u8; 32]);
2942        let new_stake_string = new_stake_authority.to_string();
2943        let new_withdraw_authority = Pubkey::from([2u8; 32]);
2944        let new_withdraw_string = new_withdraw_authority.to_string();
2945        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
2946            "test",
2947            "stake-authorize",
2948            &stake_account_string,
2949            "--new-stake-authority",
2950            &new_stake_string,
2951            "--new-withdraw-authority",
2952            &new_withdraw_string,
2953        ]);
2954        assert_eq!(
2955            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
2956            CliCommandInfo {
2957                command: CliCommand::StakeAuthorize {
2958                    stake_account_pubkey,
2959                    new_authorizations: vec![
2960                        StakeAuthorizationIndexed {
2961                            authorization_type: StakeAuthorize::Staker,
2962                            new_authority_pubkey: new_stake_authority,
2963                            authority: 0,
2964                            new_authority_signer: None,
2965                        },
2966                        StakeAuthorizationIndexed {
2967                            authorization_type: StakeAuthorize::Withdrawer,
2968                            new_authority_pubkey: new_withdraw_authority,
2969                            authority: 0,
2970                            new_authority_signer: None,
2971                        },
2972                    ],
2973                    sign_only: false,
2974                    dump_transaction_message: false,
2975                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2976                    nonce_account: None,
2977                    nonce_authority: 0,
2978                    memo: None,
2979                    fee_payer: 0,
2980                    custodian: None,
2981                    no_wait: false,
2982                    compute_unit_price: None,
2983                },
2984                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
2985            },
2986        );
2987        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
2988        let withdraw_authority_keypair = Keypair::new();
2989        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
2990        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
2991            "test",
2992            "stake-authorize",
2993            &stake_account_string,
2994            "--new-stake-authority",
2995            &new_stake_string,
2996            "--new-withdraw-authority",
2997            &new_withdraw_string,
2998            "--stake-authority",
2999            &stake_authority_keypair_file,
3000            "--withdraw-authority",
3001            &withdraw_authority_keypair_file,
3002        ]);
3003        assert_eq!(
3004            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3005            CliCommandInfo {
3006                command: CliCommand::StakeAuthorize {
3007                    stake_account_pubkey,
3008                    new_authorizations: vec![
3009                        StakeAuthorizationIndexed {
3010                            authorization_type: StakeAuthorize::Staker,
3011                            new_authority_pubkey: new_stake_authority,
3012                            authority: 1,
3013                            new_authority_signer: None,
3014                        },
3015                        StakeAuthorizationIndexed {
3016                            authorization_type: StakeAuthorize::Withdrawer,
3017                            new_authority_pubkey: new_withdraw_authority,
3018                            authority: 2,
3019                            new_authority_signer: None,
3020                        },
3021                    ],
3022                    sign_only: false,
3023                    dump_transaction_message: false,
3024                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3025                    nonce_account: None,
3026                    nonce_authority: 0,
3027                    memo: None,
3028                    fee_payer: 0,
3029                    custodian: None,
3030                    no_wait: false,
3031                    compute_unit_price: None,
3032                },
3033                signers: vec![
3034                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3035                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3036                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3037                ],
3038            },
3039        );
3040        // Withdraw authority may set both new authorities
3041        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3042            "test",
3043            "stake-authorize",
3044            &stake_account_string,
3045            "--new-stake-authority",
3046            &new_stake_string,
3047            "--new-withdraw-authority",
3048            &new_withdraw_string,
3049            "--withdraw-authority",
3050            &withdraw_authority_keypair_file,
3051        ]);
3052        assert_eq!(
3053            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3054            CliCommandInfo {
3055                command: CliCommand::StakeAuthorize {
3056                    stake_account_pubkey,
3057                    new_authorizations: vec![
3058                        StakeAuthorizationIndexed {
3059                            authorization_type: StakeAuthorize::Staker,
3060                            new_authority_pubkey: new_stake_authority,
3061                            authority: 1,
3062                            new_authority_signer: None,
3063                        },
3064                        StakeAuthorizationIndexed {
3065                            authorization_type: StakeAuthorize::Withdrawer,
3066                            new_authority_pubkey: new_withdraw_authority,
3067                            authority: 1,
3068                            new_authority_signer: None,
3069                        },
3070                    ],
3071                    sign_only: false,
3072                    dump_transaction_message: false,
3073                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3074                    nonce_account: None,
3075                    nonce_authority: 0,
3076                    memo: None,
3077                    fee_payer: 0,
3078                    custodian: None,
3079                    no_wait: false,
3080                    compute_unit_price: None,
3081                },
3082                signers: vec![
3083                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3084                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3085                ],
3086            },
3087        );
3088        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3089            "test",
3090            "stake-authorize",
3091            &stake_account_string,
3092            "--new-stake-authority",
3093            &new_stake_string,
3094        ]);
3095        assert_eq!(
3096            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3097            CliCommandInfo {
3098                command: CliCommand::StakeAuthorize {
3099                    stake_account_pubkey,
3100                    new_authorizations: vec![StakeAuthorizationIndexed {
3101                        authorization_type: StakeAuthorize::Staker,
3102                        new_authority_pubkey: new_stake_authority,
3103                        authority: 0,
3104                        new_authority_signer: None,
3105                    }],
3106                    sign_only: false,
3107                    dump_transaction_message: false,
3108                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3109                    nonce_account: None,
3110                    nonce_authority: 0,
3111                    memo: None,
3112                    fee_payer: 0,
3113                    custodian: None,
3114                    no_wait: false,
3115                    compute_unit_price: None,
3116                },
3117                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3118            },
3119        );
3120        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3121            "test",
3122            "stake-authorize",
3123            &stake_account_string,
3124            "--new-stake-authority",
3125            &new_stake_string,
3126            "--stake-authority",
3127            &stake_authority_keypair_file,
3128        ]);
3129        assert_eq!(
3130            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3131            CliCommandInfo {
3132                command: CliCommand::StakeAuthorize {
3133                    stake_account_pubkey,
3134                    new_authorizations: vec![StakeAuthorizationIndexed {
3135                        authorization_type: StakeAuthorize::Staker,
3136                        new_authority_pubkey: new_stake_authority,
3137                        authority: 1,
3138                        new_authority_signer: None,
3139                    }],
3140                    sign_only: false,
3141                    dump_transaction_message: false,
3142                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3143                    nonce_account: None,
3144                    nonce_authority: 0,
3145                    memo: None,
3146                    fee_payer: 0,
3147                    custodian: None,
3148                    no_wait: false,
3149                    compute_unit_price: None,
3150                },
3151                signers: vec![
3152                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3153                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3154                ],
3155            },
3156        );
3157        // Withdraw authority may set new stake authority
3158        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3159            "test",
3160            "stake-authorize",
3161            &stake_account_string,
3162            "--new-stake-authority",
3163            &new_stake_string,
3164            "--withdraw-authority",
3165            &withdraw_authority_keypair_file,
3166        ]);
3167        assert_eq!(
3168            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3169            CliCommandInfo {
3170                command: CliCommand::StakeAuthorize {
3171                    stake_account_pubkey,
3172                    new_authorizations: vec![StakeAuthorizationIndexed {
3173                        authorization_type: StakeAuthorize::Staker,
3174                        new_authority_pubkey: new_stake_authority,
3175                        authority: 1,
3176                        new_authority_signer: None,
3177                    }],
3178                    sign_only: false,
3179                    dump_transaction_message: false,
3180                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3181                    nonce_account: None,
3182                    nonce_authority: 0,
3183                    memo: None,
3184                    fee_payer: 0,
3185                    custodian: None,
3186                    no_wait: false,
3187                    compute_unit_price: None,
3188                },
3189                signers: vec![
3190                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3191                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3192                ],
3193            },
3194        );
3195        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3196            "test",
3197            "stake-authorize",
3198            &stake_account_string,
3199            "--new-withdraw-authority",
3200            &new_withdraw_string,
3201        ]);
3202        assert_eq!(
3203            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3204            CliCommandInfo {
3205                command: CliCommand::StakeAuthorize {
3206                    stake_account_pubkey,
3207                    new_authorizations: vec![StakeAuthorizationIndexed {
3208                        authorization_type: StakeAuthorize::Withdrawer,
3209                        new_authority_pubkey: new_withdraw_authority,
3210                        authority: 0,
3211                        new_authority_signer: None,
3212                    }],
3213                    sign_only: false,
3214                    dump_transaction_message: false,
3215                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3216                    nonce_account: None,
3217                    nonce_authority: 0,
3218                    memo: None,
3219                    fee_payer: 0,
3220                    custodian: None,
3221                    no_wait: false,
3222                    compute_unit_price: None,
3223                },
3224                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3225            },
3226        );
3227        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3228            "test",
3229            "stake-authorize",
3230            &stake_account_string,
3231            "--new-withdraw-authority",
3232            &new_withdraw_string,
3233            "--withdraw-authority",
3234            &withdraw_authority_keypair_file,
3235        ]);
3236        assert_eq!(
3237            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3238            CliCommandInfo {
3239                command: CliCommand::StakeAuthorize {
3240                    stake_account_pubkey,
3241                    new_authorizations: vec![StakeAuthorizationIndexed {
3242                        authorization_type: StakeAuthorize::Withdrawer,
3243                        new_authority_pubkey: new_withdraw_authority,
3244                        authority: 1,
3245                        new_authority_signer: None,
3246                    }],
3247                    sign_only: false,
3248                    dump_transaction_message: false,
3249                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3250                    nonce_account: None,
3251                    nonce_authority: 0,
3252                    memo: None,
3253                    fee_payer: 0,
3254                    custodian: None,
3255                    no_wait: false,
3256                    compute_unit_price: None,
3257                },
3258                signers: vec![
3259                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3260                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3261                ],
3262            },
3263        );
3264
3265        // Test Authorize Subcommand w/ no-wait
3266        let test_authorize = test_commands.clone().get_matches_from(vec![
3267            "test",
3268            "stake-authorize",
3269            &stake_account_string,
3270            "--new-stake-authority",
3271            &stake_account_string,
3272            "--no-wait",
3273        ]);
3274        assert_eq!(
3275            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3276            CliCommandInfo {
3277                command: CliCommand::StakeAuthorize {
3278                    stake_account_pubkey,
3279                    new_authorizations: vec![StakeAuthorizationIndexed {
3280                        authorization_type: StakeAuthorize::Staker,
3281                        new_authority_pubkey: stake_account_pubkey,
3282                        authority: 0,
3283                        new_authority_signer: None,
3284                    }],
3285                    sign_only: false,
3286                    dump_transaction_message: false,
3287                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3288                    nonce_account: None,
3289                    nonce_authority: 0,
3290                    memo: None,
3291                    fee_payer: 0,
3292                    custodian: None,
3293                    no_wait: true,
3294                    compute_unit_price: None,
3295                },
3296                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3297            }
3298        );
3299
3300        // stake-authorize-checked subcommand
3301        let (authority_keypair_file, mut tmp_file) = make_tmp_file();
3302        let authority_keypair = Keypair::new();
3303        write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
3304        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3305            "test",
3306            "stake-authorize-checked",
3307            &stake_account_string,
3308            "--new-stake-authority",
3309            &authority_keypair_file,
3310            "--new-withdraw-authority",
3311            &authority_keypair_file,
3312        ]);
3313        assert_eq!(
3314            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3315            CliCommandInfo {
3316                command: CliCommand::StakeAuthorize {
3317                    stake_account_pubkey,
3318                    new_authorizations: vec![
3319                        StakeAuthorizationIndexed {
3320                            authorization_type: StakeAuthorize::Staker,
3321                            new_authority_pubkey: authority_keypair.pubkey(),
3322                            authority: 0,
3323                            new_authority_signer: Some(1),
3324                        },
3325                        StakeAuthorizationIndexed {
3326                            authorization_type: StakeAuthorize::Withdrawer,
3327                            new_authority_pubkey: authority_keypair.pubkey(),
3328                            authority: 0,
3329                            new_authority_signer: Some(1),
3330                        },
3331                    ],
3332                    sign_only: false,
3333                    dump_transaction_message: false,
3334                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3335                    nonce_account: None,
3336                    nonce_authority: 0,
3337                    memo: None,
3338                    fee_payer: 0,
3339                    custodian: None,
3340                    no_wait: false,
3341                    compute_unit_price: None,
3342                },
3343                signers: vec![
3344                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3345                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3346                ],
3347            },
3348        );
3349        let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3350        let withdraw_authority_keypair = Keypair::new();
3351        write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3352        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3353            "test",
3354            "stake-authorize-checked",
3355            &stake_account_string,
3356            "--new-stake-authority",
3357            &authority_keypair_file,
3358            "--new-withdraw-authority",
3359            &authority_keypair_file,
3360            "--stake-authority",
3361            &stake_authority_keypair_file,
3362            "--withdraw-authority",
3363            &withdraw_authority_keypair_file,
3364        ]);
3365        assert_eq!(
3366            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3367            CliCommandInfo {
3368                command: CliCommand::StakeAuthorize {
3369                    stake_account_pubkey,
3370                    new_authorizations: vec![
3371                        StakeAuthorizationIndexed {
3372                            authorization_type: StakeAuthorize::Staker,
3373                            new_authority_pubkey: authority_keypair.pubkey(),
3374                            authority: 1,
3375                            new_authority_signer: Some(2),
3376                        },
3377                        StakeAuthorizationIndexed {
3378                            authorization_type: StakeAuthorize::Withdrawer,
3379                            new_authority_pubkey: authority_keypair.pubkey(),
3380                            authority: 3,
3381                            new_authority_signer: Some(2),
3382                        },
3383                    ],
3384                    sign_only: false,
3385                    dump_transaction_message: false,
3386                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3387                    nonce_account: None,
3388                    nonce_authority: 0,
3389                    memo: None,
3390                    fee_payer: 0,
3391                    custodian: None,
3392                    no_wait: false,
3393                    compute_unit_price: None,
3394                },
3395                signers: vec![
3396                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3397                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3398                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3399                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3400                ],
3401            },
3402        );
3403        // Withdraw authority may set both new authorities
3404        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3405            "test",
3406            "stake-authorize-checked",
3407            &stake_account_string,
3408            "--new-stake-authority",
3409            &authority_keypair_file,
3410            "--new-withdraw-authority",
3411            &authority_keypair_file,
3412            "--withdraw-authority",
3413            &withdraw_authority_keypair_file,
3414        ]);
3415        assert_eq!(
3416            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3417            CliCommandInfo {
3418                command: CliCommand::StakeAuthorize {
3419                    stake_account_pubkey,
3420                    new_authorizations: vec![
3421                        StakeAuthorizationIndexed {
3422                            authorization_type: StakeAuthorize::Staker,
3423                            new_authority_pubkey: authority_keypair.pubkey(),
3424                            authority: 1,
3425                            new_authority_signer: Some(2),
3426                        },
3427                        StakeAuthorizationIndexed {
3428                            authorization_type: StakeAuthorize::Withdrawer,
3429                            new_authority_pubkey: authority_keypair.pubkey(),
3430                            authority: 1,
3431                            new_authority_signer: Some(2),
3432                        },
3433                    ],
3434                    sign_only: false,
3435                    dump_transaction_message: false,
3436                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3437                    nonce_account: None,
3438                    nonce_authority: 0,
3439                    memo: None,
3440                    fee_payer: 0,
3441                    custodian: None,
3442                    no_wait: false,
3443                    compute_unit_price: None,
3444                },
3445                signers: vec![
3446                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3447                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3448                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3449                ],
3450            },
3451        );
3452        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3453            "test",
3454            "stake-authorize-checked",
3455            &stake_account_string,
3456            "--new-stake-authority",
3457            &authority_keypair_file,
3458        ]);
3459        assert_eq!(
3460            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3461            CliCommandInfo {
3462                command: CliCommand::StakeAuthorize {
3463                    stake_account_pubkey,
3464                    new_authorizations: vec![StakeAuthorizationIndexed {
3465                        authorization_type: StakeAuthorize::Staker,
3466                        new_authority_pubkey: authority_keypair.pubkey(),
3467                        authority: 0,
3468                        new_authority_signer: Some(1),
3469                    }],
3470                    sign_only: false,
3471                    dump_transaction_message: false,
3472                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3473                    nonce_account: None,
3474                    nonce_authority: 0,
3475                    memo: None,
3476                    fee_payer: 0,
3477                    custodian: None,
3478                    no_wait: false,
3479                    compute_unit_price: None,
3480                },
3481                signers: vec![
3482                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3483                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3484                ],
3485            },
3486        );
3487        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3488            "test",
3489            "stake-authorize-checked",
3490            &stake_account_string,
3491            "--new-stake-authority",
3492            &authority_keypair_file,
3493            "--stake-authority",
3494            &stake_authority_keypair_file,
3495        ]);
3496        assert_eq!(
3497            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3498            CliCommandInfo {
3499                command: CliCommand::StakeAuthorize {
3500                    stake_account_pubkey,
3501                    new_authorizations: vec![StakeAuthorizationIndexed {
3502                        authorization_type: StakeAuthorize::Staker,
3503                        new_authority_pubkey: authority_keypair.pubkey(),
3504                        authority: 1,
3505                        new_authority_signer: Some(2),
3506                    }],
3507                    sign_only: false,
3508                    dump_transaction_message: false,
3509                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3510                    nonce_account: None,
3511                    nonce_authority: 0,
3512                    memo: None,
3513                    fee_payer: 0,
3514                    custodian: None,
3515                    no_wait: false,
3516                    compute_unit_price: None,
3517                },
3518                signers: vec![
3519                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3520                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3521                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3522                ],
3523            },
3524        );
3525        // Withdraw authority may set new stake authority
3526        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3527            "test",
3528            "stake-authorize-checked",
3529            &stake_account_string,
3530            "--new-stake-authority",
3531            &authority_keypair_file,
3532            "--withdraw-authority",
3533            &withdraw_authority_keypair_file,
3534        ]);
3535        assert_eq!(
3536            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3537            CliCommandInfo {
3538                command: CliCommand::StakeAuthorize {
3539                    stake_account_pubkey,
3540                    new_authorizations: vec![StakeAuthorizationIndexed {
3541                        authorization_type: StakeAuthorize::Staker,
3542                        new_authority_pubkey: authority_keypair.pubkey(),
3543                        authority: 1,
3544                        new_authority_signer: Some(2),
3545                    }],
3546                    sign_only: false,
3547                    dump_transaction_message: false,
3548                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3549                    nonce_account: None,
3550                    nonce_authority: 0,
3551                    memo: None,
3552                    fee_payer: 0,
3553                    custodian: None,
3554                    no_wait: false,
3555                    compute_unit_price: None,
3556                },
3557                signers: vec![
3558                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3559                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3560                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3561                ],
3562            },
3563        );
3564        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3565            "test",
3566            "stake-authorize-checked",
3567            &stake_account_string,
3568            "--new-withdraw-authority",
3569            &authority_keypair_file,
3570        ]);
3571        assert_eq!(
3572            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3573            CliCommandInfo {
3574                command: CliCommand::StakeAuthorize {
3575                    stake_account_pubkey,
3576                    new_authorizations: vec![StakeAuthorizationIndexed {
3577                        authorization_type: StakeAuthorize::Withdrawer,
3578                        new_authority_pubkey: authority_keypair.pubkey(),
3579                        authority: 0,
3580                        new_authority_signer: Some(1),
3581                    }],
3582                    sign_only: false,
3583                    dump_transaction_message: false,
3584                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3585                    nonce_account: None,
3586                    nonce_authority: 0,
3587                    memo: None,
3588                    fee_payer: 0,
3589                    custodian: None,
3590                    no_wait: false,
3591                    compute_unit_price: None,
3592                },
3593                signers: vec![
3594                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3595                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3596                ],
3597            },
3598        );
3599        let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3600            "test",
3601            "stake-authorize-checked",
3602            &stake_account_string,
3603            "--new-withdraw-authority",
3604            &authority_keypair_file,
3605            "--withdraw-authority",
3606            &withdraw_authority_keypair_file,
3607        ]);
3608        assert_eq!(
3609            parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3610            CliCommandInfo {
3611                command: CliCommand::StakeAuthorize {
3612                    stake_account_pubkey,
3613                    new_authorizations: vec![StakeAuthorizationIndexed {
3614                        authorization_type: StakeAuthorize::Withdrawer,
3615                        new_authority_pubkey: authority_keypair.pubkey(),
3616                        authority: 1,
3617                        new_authority_signer: Some(2),
3618                    }],
3619                    sign_only: false,
3620                    dump_transaction_message: false,
3621                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3622                    nonce_account: None,
3623                    nonce_authority: 0,
3624                    memo: None,
3625                    fee_payer: 0,
3626                    custodian: None,
3627                    no_wait: false,
3628                    compute_unit_price: None,
3629                },
3630                signers: vec![
3631                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3632                    Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3633                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3634                ],
3635            },
3636        );
3637
3638        // Test Authorize Subcommand w/ no-wait
3639        let test_authorize = test_commands.clone().get_matches_from(vec![
3640            "test",
3641            "stake-authorize-checked",
3642            &stake_account_string,
3643            "--new-stake-authority",
3644            &authority_keypair_file,
3645            "--no-wait",
3646        ]);
3647        assert_eq!(
3648            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3649            CliCommandInfo {
3650                command: CliCommand::StakeAuthorize {
3651                    stake_account_pubkey,
3652                    new_authorizations: vec![StakeAuthorizationIndexed {
3653                        authorization_type: StakeAuthorize::Staker,
3654                        new_authority_pubkey: authority_keypair.pubkey(),
3655                        authority: 0,
3656                        new_authority_signer: Some(1),
3657                    }],
3658                    sign_only: false,
3659                    dump_transaction_message: false,
3660                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3661                    nonce_account: None,
3662                    nonce_authority: 0,
3663                    memo: None,
3664                    fee_payer: 0,
3665                    custodian: None,
3666                    no_wait: true,
3667                    compute_unit_price: None,
3668                },
3669                signers: vec![
3670                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3671                    Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3672                ],
3673            }
3674        );
3675
3676        // Test Authorize Subcommand w/ sign-only
3677        let blockhash = Hash::default();
3678        let blockhash_string = format!("{blockhash}");
3679        let test_authorize = test_commands.clone().get_matches_from(vec![
3680            "test",
3681            "stake-authorize",
3682            &stake_account_string,
3683            "--new-stake-authority",
3684            &stake_account_string,
3685            "--blockhash",
3686            &blockhash_string,
3687            "--sign-only",
3688        ]);
3689        assert_eq!(
3690            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3691            CliCommandInfo {
3692                command: CliCommand::StakeAuthorize {
3693                    stake_account_pubkey,
3694                    new_authorizations: vec![StakeAuthorizationIndexed {
3695                        authorization_type: StakeAuthorize::Staker,
3696                        new_authority_pubkey: stake_account_pubkey,
3697                        authority: 0,
3698                        new_authority_signer: None,
3699                    }],
3700                    sign_only: true,
3701                    dump_transaction_message: false,
3702                    blockhash_query: BlockhashQuery::None(blockhash),
3703                    nonce_account: None,
3704                    nonce_authority: 0,
3705                    memo: None,
3706                    fee_payer: 0,
3707                    custodian: None,
3708                    no_wait: false,
3709                    compute_unit_price: None,
3710                },
3711                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3712            }
3713        );
3714        // Test Authorize Subcommand w/ offline feepayer
3715        let keypair = Keypair::new();
3716        let pubkey = keypair.pubkey();
3717        let sig = keypair.sign_message(&[0u8]);
3718        let signer = format!("{}={}", keypair.pubkey(), sig);
3719        let test_authorize = test_commands.clone().get_matches_from(vec![
3720            "test",
3721            "stake-authorize",
3722            &stake_account_string,
3723            "--new-stake-authority",
3724            &stake_account_string,
3725            "--blockhash",
3726            &blockhash_string,
3727            "--signer",
3728            &signer,
3729            "--fee-payer",
3730            &pubkey.to_string(),
3731        ]);
3732        assert_eq!(
3733            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3734            CliCommandInfo {
3735                command: CliCommand::StakeAuthorize {
3736                    stake_account_pubkey,
3737                    new_authorizations: vec![StakeAuthorizationIndexed {
3738                        authorization_type: StakeAuthorize::Staker,
3739                        new_authority_pubkey: stake_account_pubkey,
3740                        authority: 0,
3741                        new_authority_signer: None,
3742                    }],
3743                    sign_only: false,
3744                    dump_transaction_message: false,
3745                    blockhash_query: BlockhashQuery::FeeCalculator(
3746                        blockhash_query::Source::Cluster,
3747                        blockhash
3748                    ),
3749                    nonce_account: None,
3750                    nonce_authority: 0,
3751                    memo: None,
3752                    fee_payer: 1,
3753                    custodian: None,
3754                    no_wait: false,
3755                    compute_unit_price: None,
3756                },
3757                signers: vec![
3758                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3759                    Box::new(Presigner::new(&pubkey, &sig))
3760                ],
3761            }
3762        );
3763        // Test Authorize Subcommand w/ offline fee payer and nonce authority
3764        let keypair2 = Keypair::new();
3765        let pubkey2 = keypair2.pubkey();
3766        let sig2 = keypair.sign_message(&[0u8]);
3767        let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
3768        let nonce_account = Pubkey::from([1u8; 32]);
3769        let test_authorize = test_commands.clone().get_matches_from(vec![
3770            "test",
3771            "stake-authorize",
3772            &stake_account_string,
3773            "--new-stake-authority",
3774            &stake_account_string,
3775            "--blockhash",
3776            &blockhash_string,
3777            "--signer",
3778            &signer,
3779            "--signer",
3780            &signer2,
3781            "--fee-payer",
3782            &pubkey.to_string(),
3783            "--nonce",
3784            &nonce_account.to_string(),
3785            "--nonce-authority",
3786            &pubkey2.to_string(),
3787        ]);
3788        assert_eq!(
3789            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3790            CliCommandInfo {
3791                command: CliCommand::StakeAuthorize {
3792                    stake_account_pubkey,
3793                    new_authorizations: vec![StakeAuthorizationIndexed {
3794                        authorization_type: StakeAuthorize::Staker,
3795                        new_authority_pubkey: stake_account_pubkey,
3796                        authority: 0,
3797                        new_authority_signer: None,
3798                    }],
3799                    sign_only: false,
3800                    dump_transaction_message: false,
3801                    blockhash_query: BlockhashQuery::FeeCalculator(
3802                        blockhash_query::Source::NonceAccount(nonce_account),
3803                        blockhash
3804                    ),
3805                    nonce_account: Some(nonce_account),
3806                    nonce_authority: 2,
3807                    memo: None,
3808                    fee_payer: 1,
3809                    custodian: None,
3810                    no_wait: false,
3811                    compute_unit_price: None,
3812                },
3813                signers: vec![
3814                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3815                    Box::new(Presigner::new(&pubkey, &sig)),
3816                    Box::new(Presigner::new(&pubkey2, &sig2)),
3817                ],
3818            }
3819        );
3820        // Test Authorize Subcommand w/ blockhash
3821        let test_authorize = test_commands.clone().get_matches_from(vec![
3822            "test",
3823            "stake-authorize",
3824            &stake_account_string,
3825            "--new-stake-authority",
3826            &stake_account_string,
3827            "--blockhash",
3828            &blockhash_string,
3829        ]);
3830        assert_eq!(
3831            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3832            CliCommandInfo {
3833                command: CliCommand::StakeAuthorize {
3834                    stake_account_pubkey,
3835                    new_authorizations: vec![StakeAuthorizationIndexed {
3836                        authorization_type: StakeAuthorize::Staker,
3837                        new_authority_pubkey: stake_account_pubkey,
3838                        authority: 0,
3839                        new_authority_signer: None,
3840                    }],
3841                    sign_only: false,
3842                    dump_transaction_message: false,
3843                    blockhash_query: BlockhashQuery::FeeCalculator(
3844                        blockhash_query::Source::Cluster,
3845                        blockhash
3846                    ),
3847                    nonce_account: None,
3848                    nonce_authority: 0,
3849                    memo: None,
3850                    fee_payer: 0,
3851                    custodian: None,
3852                    no_wait: false,
3853                    compute_unit_price: None,
3854                },
3855                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3856            }
3857        );
3858        // Test Authorize Subcommand w/ nonce
3859        let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
3860        let nonce_authority_keypair = Keypair::new();
3861        write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
3862        let nonce_account_pubkey = nonce_authority_keypair.pubkey();
3863        let nonce_account_string = nonce_account_pubkey.to_string();
3864        let test_authorize = test_commands.clone().get_matches_from(vec![
3865            "test",
3866            "stake-authorize",
3867            &stake_account_string,
3868            "--new-stake-authority",
3869            &stake_account_string,
3870            "--blockhash",
3871            &blockhash_string,
3872            "--nonce",
3873            &nonce_account_string,
3874            "--nonce-authority",
3875            &nonce_keypair_file,
3876        ]);
3877        assert_eq!(
3878            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3879            CliCommandInfo {
3880                command: CliCommand::StakeAuthorize {
3881                    stake_account_pubkey,
3882                    new_authorizations: vec![StakeAuthorizationIndexed {
3883                        authorization_type: StakeAuthorize::Staker,
3884                        new_authority_pubkey: stake_account_pubkey,
3885                        authority: 0,
3886                        new_authority_signer: None,
3887                    }],
3888                    sign_only: false,
3889                    dump_transaction_message: false,
3890                    blockhash_query: BlockhashQuery::FeeCalculator(
3891                        blockhash_query::Source::NonceAccount(nonce_account_pubkey),
3892                        blockhash
3893                    ),
3894                    nonce_account: Some(nonce_account_pubkey),
3895                    nonce_authority: 1,
3896                    memo: None,
3897                    fee_payer: 0,
3898                    custodian: None,
3899                    no_wait: false,
3900                    compute_unit_price: None,
3901                },
3902                signers: vec![
3903                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3904                    Box::new(nonce_authority_keypair)
3905                ],
3906            }
3907        );
3908        // Test Authorize Subcommand w/ fee-payer
3909        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
3910        let fee_payer_keypair = Keypair::new();
3911        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
3912        let fee_payer_pubkey = fee_payer_keypair.pubkey();
3913        let fee_payer_string = fee_payer_pubkey.to_string();
3914        let test_authorize = test_commands.clone().get_matches_from(vec![
3915            "test",
3916            "stake-authorize",
3917            &stake_account_string,
3918            "--new-stake-authority",
3919            &stake_account_string,
3920            "--fee-payer",
3921            &fee_payer_keypair_file,
3922        ]);
3923        assert_eq!(
3924            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3925            CliCommandInfo {
3926                command: CliCommand::StakeAuthorize {
3927                    stake_account_pubkey,
3928                    new_authorizations: vec![StakeAuthorizationIndexed {
3929                        authorization_type: StakeAuthorize::Staker,
3930                        new_authority_pubkey: stake_account_pubkey,
3931                        authority: 0,
3932                        new_authority_signer: None,
3933                    }],
3934                    sign_only: false,
3935                    dump_transaction_message: false,
3936                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
3937                    nonce_account: None,
3938                    nonce_authority: 0,
3939                    memo: None,
3940                    fee_payer: 1,
3941                    custodian: None,
3942                    no_wait: false,
3943                    compute_unit_price: None,
3944                },
3945                signers: vec![
3946                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3947                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap()),
3948                ],
3949            }
3950        );
3951        // Test Authorize Subcommand w/ absentee fee-payer
3952        let sig = fee_payer_keypair.sign_message(&[0u8]);
3953        let signer = format!("{fee_payer_string}={sig}");
3954        let test_authorize = test_commands.clone().get_matches_from(vec![
3955            "test",
3956            "stake-authorize",
3957            &stake_account_string,
3958            "--new-stake-authority",
3959            &stake_account_string,
3960            "--fee-payer",
3961            &fee_payer_string,
3962            "--blockhash",
3963            &blockhash_string,
3964            "--signer",
3965            &signer,
3966        ]);
3967        assert_eq!(
3968            parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3969            CliCommandInfo {
3970                command: CliCommand::StakeAuthorize {
3971                    stake_account_pubkey,
3972                    new_authorizations: vec![StakeAuthorizationIndexed {
3973                        authorization_type: StakeAuthorize::Staker,
3974                        new_authority_pubkey: stake_account_pubkey,
3975                        authority: 0,
3976                        new_authority_signer: None,
3977                    }],
3978                    sign_only: false,
3979                    dump_transaction_message: false,
3980                    blockhash_query: BlockhashQuery::FeeCalculator(
3981                        blockhash_query::Source::Cluster,
3982                        blockhash
3983                    ),
3984                    nonce_account: None,
3985                    nonce_authority: 0,
3986                    memo: None,
3987                    fee_payer: 1,
3988                    custodian: None,
3989                    no_wait: false,
3990                    compute_unit_price: None,
3991                },
3992                signers: vec![
3993                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3994                    Box::new(Presigner::new(&fee_payer_pubkey, &sig))
3995                ],
3996            }
3997        );
3998
3999        // Test CreateStakeAccount SubCommand
4000        let custodian = solana_pubkey::new_rand();
4001        let custodian_string = format!("{custodian}");
4002        let authorized = solana_pubkey::new_rand();
4003        let authorized_string = format!("{authorized}");
4004        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4005            "test",
4006            "create-stake-account",
4007            &keypair_file,
4008            "50",
4009            "--stake-authority",
4010            &authorized_string,
4011            "--withdraw-authority",
4012            &authorized_string,
4013            "--custodian",
4014            &custodian_string,
4015            "--lockup-epoch",
4016            "43",
4017        ]);
4018        assert_eq!(
4019            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4020            CliCommandInfo {
4021                command: CliCommand::CreateStakeAccount {
4022                    stake_account: 1,
4023                    seed: None,
4024                    staker: Some(authorized),
4025                    withdrawer: Some(authorized),
4026                    withdrawer_signer: None,
4027                    lockup: Lockup {
4028                        epoch: 43,
4029                        unix_timestamp: 0,
4030                        custodian,
4031                    },
4032                    amount: SpendAmount::Some(50_000_000_000),
4033                    sign_only: false,
4034                    dump_transaction_message: false,
4035                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4036                    nonce_account: None,
4037                    nonce_authority: 0,
4038                    memo: None,
4039                    fee_payer: 0,
4040                    from: 0,
4041                    compute_unit_price: None,
4042                },
4043                signers: vec![
4044                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4045                    Box::new(stake_account_keypair)
4046                ],
4047            }
4048        );
4049
4050        let (keypair_file, mut tmp_file) = make_tmp_file();
4051        let stake_account_keypair = Keypair::new();
4052        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4053        let stake_account_pubkey = stake_account_keypair.pubkey();
4054        let stake_account_string = stake_account_pubkey.to_string();
4055
4056        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4057            "test",
4058            "create-stake-account",
4059            &keypair_file,
4060            "50",
4061        ]);
4062
4063        assert_eq!(
4064            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4065            CliCommandInfo {
4066                command: CliCommand::CreateStakeAccount {
4067                    stake_account: 1,
4068                    seed: None,
4069                    staker: None,
4070                    withdrawer: None,
4071                    withdrawer_signer: None,
4072                    lockup: Lockup::default(),
4073                    amount: SpendAmount::Some(50_000_000_000),
4074                    sign_only: false,
4075                    dump_transaction_message: false,
4076                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4077                    nonce_account: None,
4078                    nonce_authority: 0,
4079                    memo: None,
4080                    fee_payer: 0,
4081                    from: 0,
4082                    compute_unit_price: None,
4083                },
4084                signers: vec![
4085                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4086                    Box::new(read_keypair_file(&keypair_file).unwrap())
4087                ],
4088            }
4089        );
4090        let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
4091        let withdrawer_keypair = Keypair::new();
4092        write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
4093        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4094            "test",
4095            "create-stake-account-checked",
4096            &keypair_file,
4097            "50",
4098            "--stake-authority",
4099            &authorized_string,
4100            "--withdraw-authority",
4101            &withdrawer_keypair_file,
4102        ]);
4103        assert_eq!(
4104            parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4105            CliCommandInfo {
4106                command: CliCommand::CreateStakeAccount {
4107                    stake_account: 1,
4108                    seed: None,
4109                    staker: Some(authorized),
4110                    withdrawer: Some(withdrawer_keypair.pubkey()),
4111                    withdrawer_signer: Some(2),
4112                    lockup: Lockup::default(),
4113                    amount: SpendAmount::Some(50_000_000_000),
4114                    sign_only: false,
4115                    dump_transaction_message: false,
4116                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4117                    nonce_account: None,
4118                    nonce_authority: 0,
4119                    memo: None,
4120                    fee_payer: 0,
4121                    from: 0,
4122                    compute_unit_price: None,
4123                },
4124                signers: vec![
4125                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4126                    Box::new(stake_account_keypair),
4127                    Box::new(withdrawer_keypair),
4128                ],
4129            }
4130        );
4131
4132        let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4133            "test",
4134            "create-stake-account-checked",
4135            &keypair_file,
4136            "50",
4137            "--stake-authority",
4138            &authorized_string,
4139            "--withdraw-authority",
4140            &authorized_string,
4141        ]);
4142        assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());
4143
4144        // CreateStakeAccount offline and nonce
4145        let nonce_account = Pubkey::from([1u8; 32]);
4146        let nonce_account_string = nonce_account.to_string();
4147        let offline = keypair_from_seed(&[2u8; 32]).unwrap();
4148        let offline_pubkey = offline.pubkey();
4149        let offline_string = offline_pubkey.to_string();
4150        let offline_sig = offline.sign_message(&[3u8]);
4151        let offline_signer = format!("{offline_pubkey}={offline_sig}");
4152        let nonce_hash = Hash::new_from_array([4u8; 32]);
4153        let nonce_hash_string = nonce_hash.to_string();
4154        let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4155            "test",
4156            "create-stake-account",
4157            &keypair_file,
4158            "50",
4159            "--blockhash",
4160            &nonce_hash_string,
4161            "--nonce",
4162            &nonce_account_string,
4163            "--nonce-authority",
4164            &offline_string,
4165            "--fee-payer",
4166            &offline_string,
4167            "--from",
4168            &offline_string,
4169            "--signer",
4170            &offline_signer,
4171        ]);
4172
4173        assert_eq!(
4174            parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4175            CliCommandInfo {
4176                command: CliCommand::CreateStakeAccount {
4177                    stake_account: 1,
4178                    seed: None,
4179                    staker: None,
4180                    withdrawer: None,
4181                    withdrawer_signer: None,
4182                    lockup: Lockup::default(),
4183                    amount: SpendAmount::Some(50_000_000_000),
4184                    sign_only: false,
4185                    dump_transaction_message: false,
4186                    blockhash_query: BlockhashQuery::FeeCalculator(
4187                        blockhash_query::Source::NonceAccount(nonce_account),
4188                        nonce_hash
4189                    ),
4190                    nonce_account: Some(nonce_account),
4191                    nonce_authority: 0,
4192                    memo: None,
4193                    fee_payer: 0,
4194                    from: 0,
4195                    compute_unit_price: None,
4196                },
4197                signers: vec![
4198                    Box::new(Presigner::new(&offline_pubkey, &offline_sig)),
4199                    Box::new(read_keypair_file(&keypair_file).unwrap())
4200                ],
4201            }
4202        );
4203
4204        // Test DelegateStake Subcommand
4205        let vote_account_pubkey = solana_pubkey::new_rand();
4206        let vote_account_string = vote_account_pubkey.to_string();
4207        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4208            "test",
4209            "delegate-stake",
4210            &stake_account_string,
4211            &vote_account_string,
4212        ]);
4213        assert_eq!(
4214            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4215            CliCommandInfo {
4216                command: CliCommand::DelegateStake {
4217                    stake_account_pubkey,
4218                    vote_account_pubkey,
4219                    stake_authority: 0,
4220                    force: false,
4221                    sign_only: false,
4222                    dump_transaction_message: false,
4223                    blockhash_query: BlockhashQuery::default(),
4224                    nonce_account: None,
4225                    nonce_authority: 0,
4226                    memo: None,
4227                    fee_payer: 0,
4228                    compute_unit_price: None,
4229                },
4230                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4231            }
4232        );
4233
4234        // Test DelegateStake Subcommand w/ authority
4235        let vote_account_pubkey = solana_pubkey::new_rand();
4236        let vote_account_string = vote_account_pubkey.to_string();
4237        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4238            "test",
4239            "delegate-stake",
4240            &stake_account_string,
4241            &vote_account_string,
4242            "--stake-authority",
4243            &stake_authority_keypair_file,
4244        ]);
4245        assert_eq!(
4246            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4247            CliCommandInfo {
4248                command: CliCommand::DelegateStake {
4249                    stake_account_pubkey,
4250                    vote_account_pubkey,
4251                    stake_authority: 1,
4252                    force: false,
4253                    sign_only: false,
4254                    dump_transaction_message: false,
4255                    blockhash_query: BlockhashQuery::default(),
4256                    nonce_account: None,
4257                    nonce_authority: 0,
4258                    memo: None,
4259                    fee_payer: 0,
4260                    compute_unit_price: None,
4261                },
4262                signers: vec![
4263                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4264                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4265                ],
4266            }
4267        );
4268
4269        // Test DelegateStake Subcommand w/ force
4270        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4271            "test",
4272            "delegate-stake",
4273            "--force",
4274            &stake_account_string,
4275            &vote_account_string,
4276        ]);
4277        assert_eq!(
4278            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4279            CliCommandInfo {
4280                command: CliCommand::DelegateStake {
4281                    stake_account_pubkey,
4282                    vote_account_pubkey,
4283                    stake_authority: 0,
4284                    force: true,
4285                    sign_only: false,
4286                    dump_transaction_message: false,
4287                    blockhash_query: BlockhashQuery::default(),
4288                    nonce_account: None,
4289                    nonce_authority: 0,
4290                    memo: None,
4291                    fee_payer: 0,
4292                    compute_unit_price: None,
4293                },
4294                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4295            }
4296        );
4297
4298        // Test Delegate Subcommand w/ Blockhash
4299        let blockhash = Hash::default();
4300        let blockhash_string = format!("{blockhash}");
4301        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4302            "test",
4303            "delegate-stake",
4304            &stake_account_string,
4305            &vote_account_string,
4306            "--blockhash",
4307            &blockhash_string,
4308        ]);
4309        assert_eq!(
4310            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4311            CliCommandInfo {
4312                command: CliCommand::DelegateStake {
4313                    stake_account_pubkey,
4314                    vote_account_pubkey,
4315                    stake_authority: 0,
4316                    force: false,
4317                    sign_only: false,
4318                    dump_transaction_message: false,
4319                    blockhash_query: BlockhashQuery::FeeCalculator(
4320                        blockhash_query::Source::Cluster,
4321                        blockhash
4322                    ),
4323                    nonce_account: None,
4324                    nonce_authority: 0,
4325                    memo: None,
4326                    fee_payer: 0,
4327                    compute_unit_price: None,
4328                },
4329                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4330            }
4331        );
4332
4333        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4334            "test",
4335            "delegate-stake",
4336            &stake_account_string,
4337            &vote_account_string,
4338            "--blockhash",
4339            &blockhash_string,
4340            "--sign-only",
4341        ]);
4342        assert_eq!(
4343            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4344            CliCommandInfo {
4345                command: CliCommand::DelegateStake {
4346                    stake_account_pubkey,
4347                    vote_account_pubkey,
4348                    stake_authority: 0,
4349                    force: false,
4350                    sign_only: true,
4351                    dump_transaction_message: false,
4352                    blockhash_query: BlockhashQuery::None(blockhash),
4353                    nonce_account: None,
4354                    nonce_authority: 0,
4355                    memo: None,
4356                    fee_payer: 0,
4357                    compute_unit_price: None,
4358                },
4359                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4360            }
4361        );
4362
4363        // Test Delegate Subcommand w/ absent fee payer
4364        let key1 = solana_pubkey::new_rand();
4365        let sig1 = Keypair::new().sign_message(&[0u8]);
4366        let signer1 = format!("{key1}={sig1}");
4367        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4368            "test",
4369            "delegate-stake",
4370            &stake_account_string,
4371            &vote_account_string,
4372            "--blockhash",
4373            &blockhash_string,
4374            "--signer",
4375            &signer1,
4376            "--fee-payer",
4377            &key1.to_string(),
4378        ]);
4379        assert_eq!(
4380            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4381            CliCommandInfo {
4382                command: CliCommand::DelegateStake {
4383                    stake_account_pubkey,
4384                    vote_account_pubkey,
4385                    stake_authority: 0,
4386                    force: false,
4387                    sign_only: false,
4388                    dump_transaction_message: false,
4389                    blockhash_query: BlockhashQuery::FeeCalculator(
4390                        blockhash_query::Source::Cluster,
4391                        blockhash
4392                    ),
4393                    nonce_account: None,
4394                    nonce_authority: 0,
4395                    memo: None,
4396                    fee_payer: 1,
4397                    compute_unit_price: None,
4398                },
4399                signers: vec![
4400                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4401                    Box::new(Presigner::new(&key1, &sig1))
4402                ],
4403            }
4404        );
4405
4406        // Test Delegate Subcommand w/ absent fee payer and absent nonce authority
4407        let key2 = solana_pubkey::new_rand();
4408        let sig2 = Keypair::new().sign_message(&[0u8]);
4409        let signer2 = format!("{key2}={sig2}");
4410        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4411            "test",
4412            "delegate-stake",
4413            &stake_account_string,
4414            &vote_account_string,
4415            "--blockhash",
4416            &blockhash_string,
4417            "--signer",
4418            &signer1,
4419            "--signer",
4420            &signer2,
4421            "--fee-payer",
4422            &key1.to_string(),
4423            "--nonce",
4424            &nonce_account.to_string(),
4425            "--nonce-authority",
4426            &key2.to_string(),
4427        ]);
4428        assert_eq!(
4429            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4430            CliCommandInfo {
4431                command: CliCommand::DelegateStake {
4432                    stake_account_pubkey,
4433                    vote_account_pubkey,
4434                    stake_authority: 0,
4435                    force: false,
4436                    sign_only: false,
4437                    dump_transaction_message: false,
4438                    blockhash_query: BlockhashQuery::FeeCalculator(
4439                        blockhash_query::Source::NonceAccount(nonce_account),
4440                        blockhash
4441                    ),
4442                    nonce_account: Some(nonce_account),
4443                    nonce_authority: 2,
4444                    memo: None,
4445                    fee_payer: 1,
4446                    compute_unit_price: None,
4447                },
4448                signers: vec![
4449                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4450                    Box::new(Presigner::new(&key1, &sig1)),
4451                    Box::new(Presigner::new(&key2, &sig2)),
4452                ],
4453            }
4454        );
4455
4456        // Test Delegate Subcommand w/ present fee-payer
4457        let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4458        let fee_payer_keypair = Keypair::new();
4459        write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4460        let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4461            "test",
4462            "delegate-stake",
4463            &stake_account_string,
4464            &vote_account_string,
4465            "--fee-payer",
4466            &fee_payer_keypair_file,
4467        ]);
4468        assert_eq!(
4469            parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4470            CliCommandInfo {
4471                command: CliCommand::DelegateStake {
4472                    stake_account_pubkey,
4473                    vote_account_pubkey,
4474                    stake_authority: 0,
4475                    force: false,
4476                    sign_only: false,
4477                    dump_transaction_message: false,
4478                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4479                    nonce_account: None,
4480                    nonce_authority: 0,
4481                    memo: None,
4482                    fee_payer: 1,
4483                    compute_unit_price: None,
4484                },
4485                signers: vec![
4486                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4487                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
4488                ],
4489            }
4490        );
4491
4492        // Test WithdrawStake Subcommand
4493        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4494            "test",
4495            "withdraw-stake",
4496            &stake_account_string,
4497            &stake_account_string,
4498            "42",
4499        ]);
4500
4501        assert_eq!(
4502            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4503            CliCommandInfo {
4504                command: CliCommand::WithdrawStake {
4505                    stake_account_pubkey,
4506                    destination_account_pubkey: stake_account_pubkey,
4507                    amount: SpendAmount::Some(42_000_000_000),
4508                    withdraw_authority: 0,
4509                    custodian: None,
4510                    sign_only: false,
4511                    dump_transaction_message: false,
4512                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4513                    nonce_account: None,
4514                    nonce_authority: 0,
4515                    memo: None,
4516                    seed: None,
4517                    fee_payer: 0,
4518                    compute_unit_price: None,
4519                },
4520                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4521            }
4522        );
4523
4524        // Test WithdrawStake Subcommand w/ AVAILABLE amount
4525        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4526            "test",
4527            "withdraw-stake",
4528            &stake_account_string,
4529            &stake_account_string,
4530            "AVAILABLE",
4531        ]);
4532
4533        assert_eq!(
4534            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4535            CliCommandInfo {
4536                command: CliCommand::WithdrawStake {
4537                    stake_account_pubkey,
4538                    destination_account_pubkey: stake_account_pubkey,
4539                    amount: SpendAmount::Available,
4540                    withdraw_authority: 0,
4541                    custodian: None,
4542                    sign_only: false,
4543                    dump_transaction_message: false,
4544                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4545                    nonce_account: None,
4546                    nonce_authority: 0,
4547                    memo: None,
4548                    seed: None,
4549                    fee_payer: 0,
4550                    compute_unit_price: None,
4551                },
4552                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4553            }
4554        );
4555
4556        // Test WithdrawStake Subcommand w/ ComputeUnitPrice
4557        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4558            "test",
4559            "withdraw-stake",
4560            &stake_account_string,
4561            &stake_account_string,
4562            "42",
4563            "--with-compute-unit-price",
4564            "99",
4565        ]);
4566
4567        assert_eq!(
4568            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4569            CliCommandInfo {
4570                command: CliCommand::WithdrawStake {
4571                    stake_account_pubkey,
4572                    destination_account_pubkey: stake_account_pubkey,
4573                    amount: SpendAmount::Some(42_000_000_000),
4574                    withdraw_authority: 0,
4575                    custodian: None,
4576                    sign_only: false,
4577                    dump_transaction_message: false,
4578                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4579                    nonce_account: None,
4580                    nonce_authority: 0,
4581                    memo: None,
4582                    seed: None,
4583                    fee_payer: 0,
4584                    compute_unit_price: Some(99),
4585                },
4586                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4587            }
4588        );
4589
4590        // Test WithdrawStake Subcommand w/ authority
4591        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4592            "test",
4593            "withdraw-stake",
4594            &stake_account_string,
4595            &stake_account_string,
4596            "42",
4597            "--withdraw-authority",
4598            &stake_authority_keypair_file,
4599        ]);
4600
4601        assert_eq!(
4602            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4603            CliCommandInfo {
4604                command: CliCommand::WithdrawStake {
4605                    stake_account_pubkey,
4606                    destination_account_pubkey: stake_account_pubkey,
4607                    amount: SpendAmount::Some(42_000_000_000),
4608                    withdraw_authority: 1,
4609                    custodian: None,
4610                    sign_only: false,
4611                    dump_transaction_message: false,
4612                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4613                    nonce_account: None,
4614                    nonce_authority: 0,
4615                    memo: None,
4616                    seed: None,
4617                    fee_payer: 0,
4618                    compute_unit_price: None,
4619                },
4620                signers: vec![
4621                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4622                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4623                ],
4624            }
4625        );
4626
4627        // Test WithdrawStake Subcommand w/ custodian
4628        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4629            "test",
4630            "withdraw-stake",
4631            &stake_account_string,
4632            &stake_account_string,
4633            "42",
4634            "--custodian",
4635            &custodian_keypair_file,
4636        ]);
4637
4638        assert_eq!(
4639            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4640            CliCommandInfo {
4641                command: CliCommand::WithdrawStake {
4642                    stake_account_pubkey,
4643                    destination_account_pubkey: stake_account_pubkey,
4644                    amount: SpendAmount::Some(42_000_000_000),
4645                    withdraw_authority: 0,
4646                    custodian: Some(1),
4647                    sign_only: false,
4648                    dump_transaction_message: false,
4649                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4650                    nonce_account: None,
4651                    nonce_authority: 0,
4652                    memo: None,
4653                    seed: None,
4654                    fee_payer: 0,
4655                    compute_unit_price: None,
4656                },
4657                signers: vec![
4658                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4659                    Box::new(read_keypair_file(&custodian_keypair_file).unwrap())
4660                ],
4661            }
4662        );
4663
4664        // Test WithdrawStake Subcommand w/ authority and offline nonce
4665        let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4666            "test",
4667            "withdraw-stake",
4668            &stake_account_string,
4669            &stake_account_string,
4670            "42",
4671            "--withdraw-authority",
4672            &stake_authority_keypair_file,
4673            "--blockhash",
4674            &nonce_hash_string,
4675            "--nonce",
4676            &nonce_account_string,
4677            "--nonce-authority",
4678            &offline_string,
4679            "--fee-payer",
4680            &offline_string,
4681            "--signer",
4682            &offline_signer,
4683        ]);
4684
4685        assert_eq!(
4686            parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4687            CliCommandInfo {
4688                command: CliCommand::WithdrawStake {
4689                    stake_account_pubkey,
4690                    destination_account_pubkey: stake_account_pubkey,
4691                    amount: SpendAmount::Some(42_000_000_000),
4692                    withdraw_authority: 0,
4693                    custodian: None,
4694                    sign_only: false,
4695                    dump_transaction_message: false,
4696                    blockhash_query: BlockhashQuery::FeeCalculator(
4697                        blockhash_query::Source::NonceAccount(nonce_account),
4698                        nonce_hash
4699                    ),
4700                    nonce_account: Some(nonce_account),
4701                    nonce_authority: 1,
4702                    memo: None,
4703                    seed: None,
4704                    fee_payer: 1,
4705                    compute_unit_price: None,
4706                },
4707                signers: vec![
4708                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
4709                    Box::new(Presigner::new(&offline_pubkey, &offline_sig))
4710                ],
4711            }
4712        );
4713
4714        // Test DeactivateStake Subcommand
4715        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4716            "test",
4717            "deactivate-stake",
4718            &stake_account_string,
4719        ]);
4720        assert_eq!(
4721            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4722            CliCommandInfo {
4723                command: CliCommand::DeactivateStake {
4724                    stake_account_pubkey,
4725                    stake_authority: 0,
4726                    sign_only: false,
4727                    deactivate_delinquent: false,
4728                    dump_transaction_message: false,
4729                    blockhash_query: BlockhashQuery::default(),
4730                    nonce_account: None,
4731                    nonce_authority: 0,
4732                    memo: None,
4733                    seed: None,
4734                    fee_payer: 0,
4735                    compute_unit_price: None,
4736                },
4737                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4738            }
4739        );
4740
4741        // Test DeactivateStake Subcommand with delinquent flag
4742        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4743            "test",
4744            "deactivate-stake",
4745            &stake_account_string,
4746            "--delinquent",
4747        ]);
4748        assert_eq!(
4749            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4750            CliCommandInfo {
4751                command: CliCommand::DeactivateStake {
4752                    stake_account_pubkey,
4753                    stake_authority: 0,
4754                    sign_only: false,
4755                    deactivate_delinquent: true,
4756                    dump_transaction_message: false,
4757                    blockhash_query: BlockhashQuery::default(),
4758                    nonce_account: None,
4759                    nonce_authority: 0,
4760                    memo: None,
4761                    seed: None,
4762                    fee_payer: 0,
4763                    compute_unit_price: None,
4764                },
4765                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4766            }
4767        );
4768
4769        // Test DeactivateStake Subcommand w/ authority
4770        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4771            "test",
4772            "deactivate-stake",
4773            &stake_account_string,
4774            "--stake-authority",
4775            &stake_authority_keypair_file,
4776        ]);
4777        assert_eq!(
4778            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4779            CliCommandInfo {
4780                command: CliCommand::DeactivateStake {
4781                    stake_account_pubkey,
4782                    stake_authority: 1,
4783                    sign_only: false,
4784                    deactivate_delinquent: false,
4785                    dump_transaction_message: false,
4786                    blockhash_query: BlockhashQuery::default(),
4787                    nonce_account: None,
4788                    nonce_authority: 0,
4789                    memo: None,
4790                    seed: None,
4791                    fee_payer: 0,
4792                    compute_unit_price: None,
4793                },
4794                signers: vec![
4795                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4796                    Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4797                ],
4798            }
4799        );
4800
4801        // Test Deactivate Subcommand w/ Blockhash
4802        let blockhash = Hash::default();
4803        let blockhash_string = format!("{blockhash}");
4804        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4805            "test",
4806            "deactivate-stake",
4807            &stake_account_string,
4808            "--blockhash",
4809            &blockhash_string,
4810        ]);
4811        assert_eq!(
4812            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4813            CliCommandInfo {
4814                command: CliCommand::DeactivateStake {
4815                    stake_account_pubkey,
4816                    stake_authority: 0,
4817                    sign_only: false,
4818                    deactivate_delinquent: false,
4819                    dump_transaction_message: false,
4820                    blockhash_query: BlockhashQuery::FeeCalculator(
4821                        blockhash_query::Source::Cluster,
4822                        blockhash
4823                    ),
4824                    nonce_account: None,
4825                    nonce_authority: 0,
4826                    memo: None,
4827                    seed: None,
4828                    fee_payer: 0,
4829                    compute_unit_price: None,
4830                },
4831                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4832            }
4833        );
4834
4835        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4836            "test",
4837            "deactivate-stake",
4838            &stake_account_string,
4839            "--blockhash",
4840            &blockhash_string,
4841            "--sign-only",
4842        ]);
4843        assert_eq!(
4844            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4845            CliCommandInfo {
4846                command: CliCommand::DeactivateStake {
4847                    stake_account_pubkey,
4848                    stake_authority: 0,
4849                    sign_only: true,
4850                    deactivate_delinquent: false,
4851                    dump_transaction_message: false,
4852                    blockhash_query: BlockhashQuery::None(blockhash),
4853                    nonce_account: None,
4854                    nonce_authority: 0,
4855                    memo: None,
4856                    seed: None,
4857                    fee_payer: 0,
4858                    compute_unit_price: None,
4859                },
4860                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4861            }
4862        );
4863
4864        // Test Deactivate Subcommand w/ absent fee payer
4865        let key1 = solana_pubkey::new_rand();
4866        let sig1 = Keypair::new().sign_message(&[0u8]);
4867        let signer1 = format!("{key1}={sig1}");
4868        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4869            "test",
4870            "deactivate-stake",
4871            &stake_account_string,
4872            "--blockhash",
4873            &blockhash_string,
4874            "--signer",
4875            &signer1,
4876            "--fee-payer",
4877            &key1.to_string(),
4878        ]);
4879        assert_eq!(
4880            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4881            CliCommandInfo {
4882                command: CliCommand::DeactivateStake {
4883                    stake_account_pubkey,
4884                    stake_authority: 0,
4885                    sign_only: false,
4886                    deactivate_delinquent: false,
4887                    dump_transaction_message: false,
4888                    blockhash_query: BlockhashQuery::FeeCalculator(
4889                        blockhash_query::Source::Cluster,
4890                        blockhash
4891                    ),
4892                    nonce_account: None,
4893                    nonce_authority: 0,
4894                    memo: None,
4895                    seed: None,
4896                    fee_payer: 1,
4897                    compute_unit_price: None,
4898                },
4899                signers: vec![
4900                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4901                    Box::new(Presigner::new(&key1, &sig1))
4902                ],
4903            }
4904        );
4905
4906        // Test Deactivate Subcommand w/ absent fee payer and nonce authority
4907        let key2 = solana_pubkey::new_rand();
4908        let sig2 = Keypair::new().sign_message(&[0u8]);
4909        let signer2 = format!("{key2}={sig2}");
4910        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4911            "test",
4912            "deactivate-stake",
4913            &stake_account_string,
4914            "--blockhash",
4915            &blockhash_string,
4916            "--signer",
4917            &signer1,
4918            "--signer",
4919            &signer2,
4920            "--fee-payer",
4921            &key1.to_string(),
4922            "--nonce",
4923            &nonce_account.to_string(),
4924            "--nonce-authority",
4925            &key2.to_string(),
4926        ]);
4927        assert_eq!(
4928            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4929            CliCommandInfo {
4930                command: CliCommand::DeactivateStake {
4931                    stake_account_pubkey,
4932                    stake_authority: 0,
4933                    sign_only: false,
4934                    deactivate_delinquent: false,
4935                    dump_transaction_message: false,
4936                    blockhash_query: BlockhashQuery::FeeCalculator(
4937                        blockhash_query::Source::NonceAccount(nonce_account),
4938                        blockhash
4939                    ),
4940                    nonce_account: Some(nonce_account),
4941                    nonce_authority: 2,
4942                    memo: None,
4943                    seed: None,
4944                    fee_payer: 1,
4945                    compute_unit_price: None,
4946                },
4947                signers: vec![
4948                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4949                    Box::new(Presigner::new(&key1, &sig1)),
4950                    Box::new(Presigner::new(&key2, &sig2)),
4951                ],
4952            }
4953        );
4954
4955        // Test Deactivate Subcommand w/ fee-payer
4956        let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4957            "test",
4958            "deactivate-stake",
4959            &stake_account_string,
4960            "--fee-payer",
4961            &fee_payer_keypair_file,
4962        ]);
4963        assert_eq!(
4964            parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4965            CliCommandInfo {
4966                command: CliCommand::DeactivateStake {
4967                    stake_account_pubkey,
4968                    stake_authority: 0,
4969                    sign_only: false,
4970                    deactivate_delinquent: false,
4971                    dump_transaction_message: false,
4972                    blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
4973                    nonce_account: None,
4974                    nonce_authority: 0,
4975                    memo: None,
4976                    seed: None,
4977                    fee_payer: 1,
4978                    compute_unit_price: None,
4979                },
4980                signers: vec![
4981                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4982                    Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
4983                ],
4984            }
4985        );
4986
4987        // Test SplitStake SubCommand
4988        let (keypair_file, mut tmp_file) = make_tmp_file();
4989        let stake_account_keypair = Keypair::new();
4990        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4991        let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
4992        let split_stake_account_keypair = Keypair::new();
4993        write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4994
4995        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
4996            "test",
4997            "split-stake",
4998            &keypair_file,
4999            &split_stake_account_keypair_file,
5000            "50",
5001        ]);
5002        assert_eq!(
5003            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5004            CliCommandInfo {
5005                command: CliCommand::SplitStake {
5006                    stake_account_pubkey: stake_account_keypair.pubkey(),
5007                    stake_authority: 0,
5008                    sign_only: false,
5009                    dump_transaction_message: false,
5010                    blockhash_query: BlockhashQuery::default(),
5011                    nonce_account: None,
5012                    nonce_authority: 0,
5013                    memo: None,
5014                    split_stake_account: 1,
5015                    seed: None,
5016                    lamports: 50_000_000_000,
5017                    fee_payer: 0,
5018                    compute_unit_price: None,
5019                    rent_exempt_reserve: None,
5020                },
5021                signers: vec![
5022                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5023                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap())
5024                ],
5025            }
5026        );
5027
5028        // Split stake offline nonced submission
5029        let nonce_account = Pubkey::from([1u8; 32]);
5030        let nonce_account_string = nonce_account.to_string();
5031        let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
5032        let nonce_auth_pubkey = nonce_auth.pubkey();
5033        let nonce_auth_string = nonce_auth_pubkey.to_string();
5034        let nonce_sig = nonce_auth.sign_message(&[0u8]);
5035        let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
5036        let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
5037        let stake_auth_pubkey = stake_auth.pubkey();
5038        let stake_auth_string = stake_auth_pubkey.to_string();
5039        let stake_sig = stake_auth.sign_message(&[0u8]);
5040        let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
5041        let nonce_hash = Hash::new_from_array([4u8; 32]);
5042        let nonce_hash_string = nonce_hash.to_string();
5043
5044        let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5045            "test",
5046            "split-stake",
5047            &keypair_file,
5048            &split_stake_account_keypair_file,
5049            "50",
5050            "--stake-authority",
5051            &stake_auth_string,
5052            "--blockhash",
5053            &nonce_hash_string,
5054            "--nonce",
5055            &nonce_account_string,
5056            "--nonce-authority",
5057            &nonce_auth_string,
5058            "--fee-payer",
5059            &nonce_auth_string, // Arbitrary choice of fee-payer
5060            "--signer",
5061            &nonce_signer,
5062            "--signer",
5063            &stake_signer,
5064        ]);
5065        assert_eq!(
5066            parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5067            CliCommandInfo {
5068                command: CliCommand::SplitStake {
5069                    stake_account_pubkey: stake_account_keypair.pubkey(),
5070                    stake_authority: 0,
5071                    sign_only: false,
5072                    dump_transaction_message: false,
5073                    blockhash_query: BlockhashQuery::FeeCalculator(
5074                        blockhash_query::Source::NonceAccount(nonce_account),
5075                        nonce_hash
5076                    ),
5077                    nonce_account: Some(nonce_account),
5078                    nonce_authority: 1,
5079                    memo: None,
5080                    split_stake_account: 2,
5081                    seed: None,
5082                    lamports: 50_000_000_000,
5083                    fee_payer: 1,
5084                    compute_unit_price: None,
5085                    rent_exempt_reserve: None,
5086                },
5087                signers: vec![
5088                    Box::new(Presigner::new(&stake_auth_pubkey, &stake_sig)),
5089                    Box::new(Presigner::new(&nonce_auth_pubkey, &nonce_sig)),
5090                    Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap()),
5091                ],
5092            }
5093        );
5094
5095        // Test MergeStake SubCommand
5096        let (keypair_file, mut tmp_file) = make_tmp_file();
5097        let stake_account_keypair = Keypair::new();
5098        write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5099
5100        let source_stake_account_pubkey = solana_pubkey::new_rand();
5101        let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
5102            "test",
5103            "merge-stake",
5104            &keypair_file,
5105            &source_stake_account_pubkey.to_string(),
5106        ]);
5107        assert_eq!(
5108            parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
5109            CliCommandInfo {
5110                command: CliCommand::MergeStake {
5111                    stake_account_pubkey: stake_account_keypair.pubkey(),
5112                    source_stake_account_pubkey,
5113                    stake_authority: 0,
5114                    sign_only: false,
5115                    dump_transaction_message: false,
5116                    blockhash_query: BlockhashQuery::default(),
5117                    nonce_account: None,
5118                    nonce_authority: 0,
5119                    memo: None,
5120                    fee_payer: 0,
5121                    compute_unit_price: None,
5122                },
5123                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
5124            }
5125        );
5126    }
5127}