Skip to main content

solana_cli/
stake.rs

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