solana_cli/
stake.rs

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