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