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