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()) .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 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 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(), &stake_account_address, &stake_account.pubkey(), seed, &authorized,
1423 lamports,
1424 )
1425 }
1426 (Some(seed), None) => stake_instruction::create_account_with_seed(
1427 &from.pubkey(), &stake_account_address, &stake_account.pubkey(), seed, &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, &authority.pubkey(), new_authority_pubkey, *authorization_type, custodian.map(|signer| signer.pubkey()).as_ref(),
1609 ));
1610 } else {
1611 ixs.push(stake_instruction::authorize(
1612 stake_account_pubkey, &authority.pubkey(), new_authority_pubkey, *authorization_type, 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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, "--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 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}