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