1use {
2 crate::{
3 checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4 cli::{
5 CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6 log_instruction_custom_error,
7 },
8 compute_budget::{
9 ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
10 },
11 feature::get_feature_activation_epoch,
12 memo::WithMemo,
13 nonce::check_nonce_account,
14 spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balances},
15 },
16 clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand, value_t},
17 solana_account::{Account, from_account, state_traits::StateMut},
18 solana_clap_utils::{
19 ArgConstant,
20 compute_budget::{COMPUTE_UNIT_PRICE_ARG, ComputeUnitLimit, compute_unit_price_arg},
21 fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
22 hidden_unless_forced,
23 input_parsers::*,
24 input_validators::*,
25 keypair::{DefaultSigner, SignerIndex},
26 memo::{MEMO_ARG, memo_arg},
27 nonce::*,
28 offline::*,
29 },
30 solana_cli_output::{
31 self, CliBalance, CliEpochReward, CliStakeHistory, CliStakeHistoryEntry, CliStakeState,
32 CliStakeType, OutputFormat, ReturnSignersConfig, display::BuildBalanceMessageConfig,
33 return_signers_with_config,
34 },
35 solana_clock::{Clock, Epoch, SECONDS_PER_DAY, UnixTimestamp},
36 solana_commitment_config::CommitmentConfig,
37 solana_epoch_schedule::EpochSchedule,
38 solana_message::Message,
39 solana_native_token::Sol,
40 solana_pubkey::Pubkey,
41 solana_remote_wallet::remote_wallet::RemoteWalletManager,
42 solana_rpc_client::nonblocking::rpc_client::RpcClient,
43 solana_rpc_client_api::{
44 config::RpcGetVoteAccountsConfig,
45 request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
46 response::{RpcInflationReward, RpcVoteAccountStatus},
47 },
48 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
49 solana_sdk_ids::{
50 system_program,
51 sysvar::{clock, stake_history},
52 },
53 solana_stake_interface::{
54 self as stake,
55 error::StakeError,
56 instruction::{self as stake_instruction, LockupArgs},
57 stake_history::StakeHistory,
58 state::{
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,
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 check_account_for_fee_with_commitment(
1690 rpc_client,
1691 &tx.message.account_keys[0],
1692 &tx.message,
1693 config.commitment,
1694 )
1695 .await?;
1696 let result = if no_wait {
1697 rpc_client
1698 .send_transaction_with_config(&tx, config.send_transaction_config)
1699 .await
1700 } else {
1701 rpc_client
1702 .send_and_confirm_transaction_with_spinner_and_config(
1703 &tx,
1704 config.commitment,
1705 config.send_transaction_config,
1706 )
1707 .await
1708 };
1709 log_instruction_custom_error::<StakeError>(result, config)
1710 }
1711}
1712
1713#[allow(clippy::too_many_arguments)]
1714pub async fn process_deactivate_stake_account(
1715 rpc_client: &RpcClient,
1716 config: &CliConfig<'_>,
1717 stake_account_pubkey: &Pubkey,
1718 stake_authority: SignerIndex,
1719 sign_only: bool,
1720 deactivate_delinquent: bool,
1721 dump_transaction_message: bool,
1722 blockhash_query: &BlockhashQuery,
1723 nonce_account: Option<Pubkey>,
1724 nonce_authority: SignerIndex,
1725 memo: Option<&String>,
1726 seed: Option<&String>,
1727 fee_payer: SignerIndex,
1728 compute_unit_price: Option<u64>,
1729) -> ProcessResult {
1730 let recent_blockhash = blockhash_query
1731 .get_blockhash(rpc_client, config.commitment)
1732 .await?;
1733
1734 let stake_account_address = if let Some(seed) = seed {
1735 Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1736 } else {
1737 *stake_account_pubkey
1738 };
1739
1740 let compute_unit_limit = match blockhash_query {
1742 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1743 BlockhashQuery::Rpc(_) if deactivate_delinquent => {
1744 ComputeUnitLimit::SimulatedWithExtraPercentage(5)
1745 }
1746 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1747 };
1748 let ixs = vec![if deactivate_delinquent {
1749 let stake_account = rpc_client.get_account(&stake_account_address).await?;
1750 if stake_account.owner != stake::program::id() {
1751 return Err(CliError::BadParameter(format!(
1752 "{stake_account_address} is not a stake account",
1753 ))
1754 .into());
1755 }
1756
1757 let vote_account_address = match stake_account.state() {
1758 Ok(stake_state) => match stake_state {
1759 StakeStateV2::Stake(_, stake, _) => stake.delegation.voter_pubkey,
1760 _ => {
1761 return Err(CliError::BadParameter(format!(
1762 "{stake_account_address} is not a delegated stake account",
1763 ))
1764 .into());
1765 }
1766 },
1767 Err(err) => {
1768 return Err(CliError::RpcRequestError(format!(
1769 "Account data could not be deserialized to stake state: {err}"
1770 ))
1771 .into());
1772 }
1773 };
1774
1775 let current_epoch = rpc_client.get_epoch_info().await?.epoch;
1776
1777 let (_, vote_state) = crate::vote::get_vote_account(
1778 rpc_client,
1779 &vote_account_address,
1780 rpc_client.commitment(),
1781 )
1782 .await?;
1783 if !eligible_for_deactivate_delinquent(&vote_state.epoch_credits, current_epoch) {
1784 return Err(CliError::BadParameter(format!(
1785 "Stake has not been delinquent for {} epochs",
1786 stake::MINIMUM_DELINQUENT_EPOCHS_FOR_DEACTIVATION,
1787 ))
1788 .into());
1789 }
1790
1791 let reference_vote_account_address = rpc_client
1793 .get_vote_accounts()
1794 .await?
1795 .current
1796 .into_iter()
1797 .find(|vote_account_info| {
1798 acceptable_reference_epoch_credits(&vote_account_info.epoch_credits, current_epoch)
1799 });
1800 let reference_vote_account_address = reference_vote_account_address
1801 .ok_or_else(|| {
1802 CliError::RpcRequestError("Unable to find a reference vote account".into())
1803 })?
1804 .vote_pubkey
1805 .parse()?;
1806
1807 stake_instruction::deactivate_delinquent_stake(
1808 &stake_account_address,
1809 &vote_account_address,
1810 &reference_vote_account_address,
1811 )
1812 } else {
1813 let stake_authority = config.signers[stake_authority];
1814 stake_instruction::deactivate_stake(&stake_account_address, &stake_authority.pubkey())
1815 }]
1816 .with_memo(memo)
1817 .with_compute_unit_config(&ComputeUnitConfig {
1818 compute_unit_price,
1819 compute_unit_limit,
1820 });
1821
1822 let nonce_authority = config.signers[nonce_authority];
1823 let fee_payer = config.signers[fee_payer];
1824
1825 let mut message = if let Some(nonce_account) = &nonce_account {
1826 Message::new_with_nonce(
1827 ixs,
1828 Some(&fee_payer.pubkey()),
1829 nonce_account,
1830 &nonce_authority.pubkey(),
1831 )
1832 } else {
1833 Message::new(&ixs, Some(&fee_payer.pubkey()))
1834 };
1835 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1836 let mut tx = Transaction::new_unsigned(message);
1837
1838 if sign_only {
1839 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1840 return_signers_with_config(
1841 &tx,
1842 &config.output_format,
1843 &ReturnSignersConfig {
1844 dump_transaction_message,
1845 },
1846 )
1847 } else {
1848 tx.try_sign(&config.signers, recent_blockhash)?;
1849 if let Some(nonce_account) = &nonce_account {
1850 let nonce_account =
1851 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1852 rpc_client,
1853 nonce_account,
1854 config.commitment,
1855 )
1856 .await?;
1857 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1858 }
1859 check_account_for_fee_with_commitment(
1860 rpc_client,
1861 &tx.message.account_keys[0],
1862 &tx.message,
1863 config.commitment,
1864 )
1865 .await?;
1866 let result = rpc_client
1867 .send_and_confirm_transaction_with_spinner_and_config(
1868 &tx,
1869 config.commitment,
1870 config.send_transaction_config,
1871 )
1872 .await;
1873 log_instruction_custom_error::<StakeError>(result, config)
1874 }
1875}
1876
1877#[allow(clippy::too_many_arguments)]
1878pub async fn process_withdraw_stake(
1879 rpc_client: &RpcClient,
1880 config: &CliConfig<'_>,
1881 stake_account_pubkey: &Pubkey,
1882 destination_account_pubkey: &Pubkey,
1883 amount: SpendAmount,
1884 withdraw_authority: SignerIndex,
1885 custodian: Option<SignerIndex>,
1886 sign_only: bool,
1887 dump_transaction_message: bool,
1888 blockhash_query: &BlockhashQuery,
1889 nonce_account: Option<&Pubkey>,
1890 nonce_authority: SignerIndex,
1891 memo: Option<&String>,
1892 seed: Option<&String>,
1893 fee_payer: SignerIndex,
1894 compute_unit_price: Option<u64>,
1895) -> ProcessResult {
1896 let withdraw_authority = config.signers[withdraw_authority];
1897 let custodian = custodian.map(|index| config.signers[index]);
1898
1899 let stake_account_address = if let Some(seed) = seed {
1900 Pubkey::create_with_seed(stake_account_pubkey, seed, &stake::program::id())?
1901 } else {
1902 *stake_account_pubkey
1903 };
1904
1905 let recent_blockhash = blockhash_query
1906 .get_blockhash(rpc_client, config.commitment)
1907 .await?;
1908
1909 let fee_payer = config.signers[fee_payer];
1910 let nonce_authority = config.signers[nonce_authority];
1911
1912 let compute_unit_limit = match blockhash_query {
1913 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1914 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1915 };
1916 let build_message = |lamports| {
1917 let ixs = vec![stake_instruction::withdraw(
1918 &stake_account_address,
1919 &withdraw_authority.pubkey(),
1920 destination_account_pubkey,
1921 lamports,
1922 custodian.map(|signer| signer.pubkey()).as_ref(),
1923 )]
1924 .with_memo(memo)
1925 .with_compute_unit_config(&ComputeUnitConfig {
1926 compute_unit_price,
1927 compute_unit_limit,
1928 });
1929
1930 if let Some(nonce_account) = &nonce_account {
1931 Message::new_with_nonce(
1932 ixs,
1933 Some(&fee_payer.pubkey()),
1934 nonce_account,
1935 &nonce_authority.pubkey(),
1936 )
1937 } else {
1938 Message::new(&ixs, Some(&fee_payer.pubkey()))
1939 }
1940 };
1941
1942 let (message, _) = resolve_spend_tx_and_check_account_balances(
1943 rpc_client,
1944 sign_only,
1945 amount,
1946 &recent_blockhash,
1947 &stake_account_address,
1948 &fee_payer.pubkey(),
1949 compute_unit_limit,
1950 build_message,
1951 config.commitment,
1952 )
1953 .await?;
1954
1955 let mut tx = Transaction::new_unsigned(message);
1956
1957 if sign_only {
1958 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1959 return_signers_with_config(
1960 &tx,
1961 &config.output_format,
1962 &ReturnSignersConfig {
1963 dump_transaction_message,
1964 },
1965 )
1966 } else {
1967 tx.try_sign(&config.signers, recent_blockhash)?;
1968 if let Some(nonce_account) = &nonce_account {
1969 let nonce_account =
1970 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1971 rpc_client,
1972 nonce_account,
1973 config.commitment,
1974 )
1975 .await?;
1976 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1977 }
1978 check_account_for_fee_with_commitment(
1979 rpc_client,
1980 &tx.message.account_keys[0],
1981 &tx.message,
1982 config.commitment,
1983 )
1984 .await?;
1985 let result = rpc_client
1986 .send_and_confirm_transaction_with_spinner_and_config(
1987 &tx,
1988 config.commitment,
1989 config.send_transaction_config,
1990 )
1991 .await;
1992 log_instruction_custom_error::<StakeError>(result, config)
1993 }
1994}
1995
1996#[allow(clippy::too_many_arguments)]
1997pub async fn process_split_stake(
1998 rpc_client: &RpcClient,
1999 config: &CliConfig<'_>,
2000 stake_account_pubkey: &Pubkey,
2001 stake_authority: SignerIndex,
2002 sign_only: bool,
2003 dump_transaction_message: bool,
2004 blockhash_query: &BlockhashQuery,
2005 nonce_account: Option<Pubkey>,
2006 nonce_authority: SignerIndex,
2007 memo: Option<&String>,
2008 split_stake_account: SignerIndex,
2009 split_stake_account_seed: &Option<String>,
2010 lamports: u64,
2011 fee_payer: SignerIndex,
2012 compute_unit_price: Option<u64>,
2013 rent_exempt_reserve: Option<&u64>,
2014) -> ProcessResult {
2015 let split_stake_account = config.signers[split_stake_account];
2016 let fee_payer = config.signers[fee_payer];
2017
2018 if split_stake_account_seed.is_none() {
2019 check_unique_pubkeys(
2020 (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2021 (
2022 &split_stake_account.pubkey(),
2023 "split_stake_account".to_string(),
2024 ),
2025 )?;
2026 }
2027 check_unique_pubkeys(
2028 (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2029 (stake_account_pubkey, "stake_account".to_string()),
2030 )?;
2031 check_unique_pubkeys(
2032 (stake_account_pubkey, "stake_account".to_string()),
2033 (
2034 &split_stake_account.pubkey(),
2035 "split_stake_account".to_string(),
2036 ),
2037 )?;
2038
2039 let stake_authority = config.signers[stake_authority];
2040
2041 let split_stake_account_address = if let Some(seed) = split_stake_account_seed {
2042 Pubkey::create_with_seed(&split_stake_account.pubkey(), seed, &stake::program::id())?
2043 } else {
2044 split_stake_account.pubkey()
2045 };
2046
2047 let rent_exempt_reserve = if let Some(rent_exempt_reserve) = rent_exempt_reserve {
2048 *rent_exempt_reserve
2049 } else {
2050 let stake_minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2051 if lamports < stake_minimum_delegation {
2052 let lamports = Sol(lamports);
2053 let stake_minimum_delegation = Sol(stake_minimum_delegation);
2054 return Err(CliError::BadParameter(format!(
2055 "need at least {stake_minimum_delegation} for minimum stake delegation, provided: \
2056 {lamports}"
2057 ))
2058 .into());
2059 }
2060
2061 let check_stake_account = |account: Account| -> Result<u64, CliError> {
2062 match account.owner {
2063 owner if owner == stake::program::id() => Err(CliError::BadParameter(format!(
2064 "Stake account {split_stake_account_address} already exists"
2065 ))),
2066 owner if owner == system_program::id() => {
2067 if !account.data.is_empty() {
2068 Err(CliError::BadParameter(format!(
2069 "Account {split_stake_account_address} has data and cannot be used to \
2070 split stake"
2071 )))
2072 } else {
2073 Ok(account.lamports)
2076 }
2077 }
2078 _ => Err(CliError::BadParameter(format!(
2079 "Account {split_stake_account_address} already exists and cannot be used to \
2080 split stake"
2081 ))),
2082 }
2083 };
2084 let current_balance =
2085 if let Ok(stake_account) = rpc_client.get_account(&split_stake_account_address).await {
2086 check_stake_account(stake_account)?
2087 } else {
2088 0
2089 };
2090
2091 let rent_exempt_reserve = rpc_client
2092 .get_minimum_balance_for_rent_exemption(StakeStateV2::size_of())
2093 .await?;
2094
2095 rent_exempt_reserve.saturating_sub(current_balance)
2096 };
2097
2098 let recent_blockhash = blockhash_query
2099 .get_blockhash(rpc_client, config.commitment)
2100 .await?;
2101
2102 let mut ixs = vec![];
2103 if rent_exempt_reserve > 0 {
2104 ixs.push(system_instruction::transfer(
2105 &fee_payer.pubkey(),
2106 &split_stake_account_address,
2107 rent_exempt_reserve,
2108 ));
2109 }
2110 let compute_unit_limit = match blockhash_query {
2111 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2112 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2113 };
2114 if let Some(seed) = split_stake_account_seed {
2115 ixs.append(
2116 &mut stake_instruction::split_with_seed(
2117 stake_account_pubkey,
2118 &stake_authority.pubkey(),
2119 lamports,
2120 &split_stake_account_address,
2121 &split_stake_account.pubkey(),
2122 seed,
2123 )
2124 .with_memo(memo)
2125 .with_compute_unit_config(&ComputeUnitConfig {
2126 compute_unit_price,
2127 compute_unit_limit,
2128 }),
2129 )
2130 } else {
2131 ixs.append(
2132 &mut stake_instruction::split(
2133 stake_account_pubkey,
2134 &stake_authority.pubkey(),
2135 lamports,
2136 &split_stake_account_address,
2137 )
2138 .with_memo(memo)
2139 .with_compute_unit_config(&ComputeUnitConfig {
2140 compute_unit_price,
2141 compute_unit_limit,
2142 }),
2143 )
2144 };
2145
2146 let nonce_authority = config.signers[nonce_authority];
2147
2148 let mut message = if let Some(nonce_account) = &nonce_account {
2149 Message::new_with_nonce(
2150 ixs,
2151 Some(&fee_payer.pubkey()),
2152 nonce_account,
2153 &nonce_authority.pubkey(),
2154 )
2155 } else {
2156 Message::new(&ixs, Some(&fee_payer.pubkey()))
2157 };
2158 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2159 let mut tx = Transaction::new_unsigned(message);
2160
2161 if sign_only {
2162 tx.try_partial_sign(&config.signers, recent_blockhash)?;
2163 return_signers_with_config(
2164 &tx,
2165 &config.output_format,
2166 &ReturnSignersConfig {
2167 dump_transaction_message,
2168 },
2169 )
2170 } else {
2171 tx.try_sign(&config.signers, recent_blockhash)?;
2172 if let Some(nonce_account) = &nonce_account {
2173 let nonce_account =
2174 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2175 rpc_client,
2176 nonce_account,
2177 config.commitment,
2178 )
2179 .await?;
2180 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2181 }
2182 check_account_for_fee_with_commitment(
2183 rpc_client,
2184 &tx.message.account_keys[0],
2185 &tx.message,
2186 config.commitment,
2187 )
2188 .await?;
2189 let result = rpc_client
2190 .send_and_confirm_transaction_with_spinner_and_config(
2191 &tx,
2192 config.commitment,
2193 config.send_transaction_config,
2194 )
2195 .await;
2196 log_instruction_custom_error::<StakeError>(result, config)
2197 }
2198}
2199
2200#[allow(clippy::too_many_arguments)]
2201pub async fn process_merge_stake(
2202 rpc_client: &RpcClient,
2203 config: &CliConfig<'_>,
2204 stake_account_pubkey: &Pubkey,
2205 source_stake_account_pubkey: &Pubkey,
2206 stake_authority: SignerIndex,
2207 sign_only: bool,
2208 dump_transaction_message: bool,
2209 blockhash_query: &BlockhashQuery,
2210 nonce_account: Option<Pubkey>,
2211 nonce_authority: SignerIndex,
2212 memo: Option<&String>,
2213 fee_payer: SignerIndex,
2214 compute_unit_price: Option<u64>,
2215) -> ProcessResult {
2216 let fee_payer = config.signers[fee_payer];
2217
2218 check_unique_pubkeys(
2219 (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2220 (stake_account_pubkey, "stake_account".to_string()),
2221 )?;
2222 check_unique_pubkeys(
2223 (&fee_payer.pubkey(), "fee-payer keypair".to_string()),
2224 (
2225 source_stake_account_pubkey,
2226 "source_stake_account".to_string(),
2227 ),
2228 )?;
2229 check_unique_pubkeys(
2230 (stake_account_pubkey, "stake_account".to_string()),
2231 (
2232 source_stake_account_pubkey,
2233 "source_stake_account".to_string(),
2234 ),
2235 )?;
2236
2237 let stake_authority = config.signers[stake_authority];
2238
2239 if !sign_only {
2240 for stake_account_address in &[stake_account_pubkey, source_stake_account_pubkey] {
2241 if let Ok(stake_account) = rpc_client.get_account(stake_account_address).await
2242 && stake_account.owner != stake::program::id()
2243 {
2244 return Err(CliError::BadParameter(format!(
2245 "Account {stake_account_address} is not a stake account"
2246 ))
2247 .into());
2248 }
2249 }
2250 }
2251
2252 let recent_blockhash = blockhash_query
2253 .get_blockhash(rpc_client, config.commitment)
2254 .await?;
2255
2256 let compute_unit_limit = match blockhash_query {
2257 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2258 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2259 };
2260 let ixs = stake_instruction::merge(
2261 stake_account_pubkey,
2262 source_stake_account_pubkey,
2263 &stake_authority.pubkey(),
2264 )
2265 .with_memo(memo)
2266 .with_compute_unit_config(&ComputeUnitConfig {
2267 compute_unit_price,
2268 compute_unit_limit,
2269 });
2270
2271 let nonce_authority = config.signers[nonce_authority];
2272
2273 let mut message = if let Some(nonce_account) = &nonce_account {
2274 Message::new_with_nonce(
2275 ixs,
2276 Some(&fee_payer.pubkey()),
2277 nonce_account,
2278 &nonce_authority.pubkey(),
2279 )
2280 } else {
2281 Message::new(&ixs, Some(&fee_payer.pubkey()))
2282 };
2283 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2284 let mut tx = Transaction::new_unsigned(message);
2285
2286 if sign_only {
2287 tx.try_partial_sign(&config.signers, recent_blockhash)?;
2288 return_signers_with_config(
2289 &tx,
2290 &config.output_format,
2291 &ReturnSignersConfig {
2292 dump_transaction_message,
2293 },
2294 )
2295 } else {
2296 tx.try_sign(&config.signers, recent_blockhash)?;
2297 if let Some(nonce_account) = &nonce_account {
2298 let nonce_account =
2299 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2300 rpc_client,
2301 nonce_account,
2302 config.commitment,
2303 )
2304 .await?;
2305 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2306 }
2307 check_account_for_fee_with_commitment(
2308 rpc_client,
2309 &tx.message.account_keys[0],
2310 &tx.message,
2311 config.commitment,
2312 )
2313 .await?;
2314 let result = rpc_client
2315 .send_and_confirm_transaction_with_spinner_and_config(
2316 &tx,
2317 config.commitment,
2318 config.send_transaction_config,
2319 )
2320 .await;
2321 log_instruction_custom_error::<StakeError>(result, config)
2322 }
2323}
2324
2325#[allow(clippy::too_many_arguments)]
2326pub async fn process_stake_set_lockup(
2327 rpc_client: &RpcClient,
2328 config: &CliConfig<'_>,
2329 stake_account_pubkey: &Pubkey,
2330 lockup: &LockupArgs,
2331 new_custodian_signer: Option<SignerIndex>,
2332 custodian: SignerIndex,
2333 sign_only: bool,
2334 dump_transaction_message: bool,
2335 blockhash_query: &BlockhashQuery,
2336 nonce_account: Option<Pubkey>,
2337 nonce_authority: SignerIndex,
2338 memo: Option<&String>,
2339 fee_payer: SignerIndex,
2340 compute_unit_price: Option<u64>,
2341) -> ProcessResult {
2342 let recent_blockhash = blockhash_query
2343 .get_blockhash(rpc_client, config.commitment)
2344 .await?;
2345 let custodian = config.signers[custodian];
2346
2347 let compute_unit_limit = match blockhash_query {
2348 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2349 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
2350 };
2351 let ixs = vec![if new_custodian_signer.is_some() {
2352 stake_instruction::set_lockup_checked(stake_account_pubkey, lockup, &custodian.pubkey())
2353 } else {
2354 stake_instruction::set_lockup(stake_account_pubkey, lockup, &custodian.pubkey())
2355 }]
2356 .with_memo(memo)
2357 .with_compute_unit_config(&ComputeUnitConfig {
2358 compute_unit_price,
2359 compute_unit_limit,
2360 });
2361 let nonce_authority = config.signers[nonce_authority];
2362 let fee_payer = config.signers[fee_payer];
2363
2364 if !sign_only {
2365 let state =
2366 get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment).await?;
2367 let lockup = match state {
2368 StakeStateV2::Stake(Meta { lockup, .. }, ..) => Some(lockup),
2369 StakeStateV2::Initialized(Meta { lockup, .. }) => Some(lockup),
2370 _ => None,
2371 };
2372 if let Some(lockup) = lockup {
2373 if lockup.custodian != Pubkey::default() {
2374 check_current_authority(&[lockup.custodian], &custodian.pubkey())?;
2375 }
2376 } else {
2377 return Err(CliError::RpcRequestError(format!(
2378 "{stake_account_pubkey:?} is not an Initialized or Delegated stake account",
2379 ))
2380 .into());
2381 }
2382 }
2383
2384 let mut message = if let Some(nonce_account) = &nonce_account {
2385 Message::new_with_nonce(
2386 ixs,
2387 Some(&fee_payer.pubkey()),
2388 nonce_account,
2389 &nonce_authority.pubkey(),
2390 )
2391 } else {
2392 Message::new(&ixs, Some(&fee_payer.pubkey()))
2393 };
2394 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2395 let mut tx = Transaction::new_unsigned(message);
2396
2397 if sign_only {
2398 tx.try_partial_sign(&config.signers, recent_blockhash)?;
2399 return_signers_with_config(
2400 &tx,
2401 &config.output_format,
2402 &ReturnSignersConfig {
2403 dump_transaction_message,
2404 },
2405 )
2406 } else {
2407 tx.try_sign(&config.signers, recent_blockhash)?;
2408 if let Some(nonce_account) = &nonce_account {
2409 let nonce_account =
2410 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2411 rpc_client,
2412 nonce_account,
2413 config.commitment,
2414 )
2415 .await?;
2416 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2417 }
2418 check_account_for_fee_with_commitment(
2419 rpc_client,
2420 &tx.message.account_keys[0],
2421 &tx.message,
2422 config.commitment,
2423 )
2424 .await?;
2425 let result = rpc_client
2426 .send_and_confirm_transaction_with_spinner_and_config(
2427 &tx,
2428 config.commitment,
2429 config.send_transaction_config,
2430 )
2431 .await;
2432 log_instruction_custom_error::<StakeError>(result, config)
2433 }
2434}
2435
2436fn u64_some_if_not_zero(n: u64) -> Option<u64> {
2437 if n > 0 { Some(n) } else { None }
2438}
2439
2440fn stake_activation_status(
2441 delegation: &Delegation,
2442 current_epoch: Epoch,
2443 stake_history: &StakeHistory,
2444 new_rate_activation_epoch: Option<Epoch>,
2445 use_fixed_point_stake_math: bool,
2446) -> StakeActivationStatus {
2447 if use_fixed_point_stake_math {
2448 delegation.stake_activating_and_deactivating_v2(
2449 current_epoch,
2450 stake_history,
2451 new_rate_activation_epoch,
2452 )
2453 } else {
2454 #[allow(deprecated)]
2455 delegation.stake_activating_and_deactivating(
2456 current_epoch,
2457 stake_history,
2458 new_rate_activation_epoch,
2459 )
2460 }
2461}
2462
2463pub fn build_stake_state(
2464 account_balance: u64,
2465 stake_state: &StakeStateV2,
2466 use_lamports_unit: bool,
2467 stake_history: &StakeHistory,
2468 clock: &Clock,
2469 new_rate_activation_epoch: Option<Epoch>,
2470 rent_exempt_reserve: u64,
2471 use_csv: bool,
2472 use_fixed_point_stake_math: bool,
2473) -> CliStakeState {
2474 match stake_state {
2475 StakeStateV2::Stake(
2476 Meta {
2477 #[expect(deprecated)]
2478 rent_exempt_reserve: _,
2479 authorized,
2480 lockup,
2481 },
2482 stake,
2483 _,
2484 ) => {
2485 let current_epoch = clock.epoch;
2486 let StakeActivationStatus {
2487 effective,
2488 activating,
2489 deactivating,
2490 } = stake_activation_status(
2491 &stake.delegation,
2492 current_epoch,
2493 stake_history,
2494 new_rate_activation_epoch,
2495 use_fixed_point_stake_math,
2496 );
2497 let lockup = if lockup.is_in_force(clock, None) {
2498 Some(lockup.into())
2499 } else {
2500 None
2501 };
2502 CliStakeState {
2503 stake_type: CliStakeType::Stake,
2504 account_balance,
2505 credits_observed: Some(stake.credits_observed),
2506 delegated_stake: Some(stake.delegation.stake),
2507 delegated_vote_account_address: if stake.delegation.voter_pubkey
2508 != Pubkey::default()
2509 {
2510 Some(stake.delegation.voter_pubkey.to_string())
2511 } else {
2512 None
2513 },
2514 activation_epoch: Some(if stake.delegation.activation_epoch < u64::MAX {
2515 stake.delegation.activation_epoch
2516 } else {
2517 0
2518 }),
2519 deactivation_epoch: if stake.delegation.deactivation_epoch < u64::MAX {
2520 Some(stake.delegation.deactivation_epoch)
2521 } else {
2522 None
2523 },
2524 authorized: Some(authorized.into()),
2525 lockup,
2526 use_lamports_unit,
2527 current_epoch,
2528 rent_exempt_reserve: Some(rent_exempt_reserve),
2529 active_stake: u64_some_if_not_zero(effective),
2530 activating_stake: u64_some_if_not_zero(activating),
2531 deactivating_stake: u64_some_if_not_zero(deactivating),
2532 use_csv,
2533 ..CliStakeState::default()
2534 }
2535 }
2536 StakeStateV2::RewardsPool => CliStakeState {
2537 stake_type: CliStakeType::RewardsPool,
2538 account_balance,
2539 ..CliStakeState::default()
2540 },
2541 StakeStateV2::Uninitialized => CliStakeState {
2542 account_balance,
2543 ..CliStakeState::default()
2544 },
2545 StakeStateV2::Initialized(Meta {
2546 #[expect(deprecated)]
2547 rent_exempt_reserve: _,
2548 authorized,
2549 lockup,
2550 }) => {
2551 let lockup = if lockup.is_in_force(clock, None) {
2552 Some(lockup.into())
2553 } else {
2554 None
2555 };
2556 CliStakeState {
2557 stake_type: CliStakeType::Initialized,
2558 account_balance,
2559 credits_observed: Some(0),
2560 authorized: Some(authorized.into()),
2561 lockup,
2562 use_lamports_unit,
2563 rent_exempt_reserve: Some(rent_exempt_reserve),
2564 ..CliStakeState::default()
2565 }
2566 }
2567 }
2568}
2569
2570async fn get_stake_account_state(
2571 rpc_client: &RpcClient,
2572 stake_account_pubkey: &Pubkey,
2573 commitment_config: CommitmentConfig,
2574) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
2575 let stake_account = rpc_client
2576 .get_account_with_commitment(stake_account_pubkey, commitment_config)
2577 .await?
2578 .value
2579 .ok_or_else(|| {
2580 CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
2581 })?;
2582 if stake_account.owner != stake::program::id() {
2583 return Err(CliError::RpcRequestError(format!(
2584 "{stake_account_pubkey:?} is not a stake account",
2585 ))
2586 .into());
2587 }
2588 stake_account.state().map_err(|err| {
2589 CliError::RpcRequestError(format!(
2590 "Account data could not be deserialized to stake state: {err}"
2591 ))
2592 .into()
2593 })
2594}
2595
2596pub(crate) fn check_current_authority(
2597 permitted_authorities: &[Pubkey],
2598 provided_current_authority: &Pubkey,
2599) -> Result<(), CliError> {
2600 if !permitted_authorities.contains(provided_current_authority) {
2601 Err(CliError::RpcRequestError(format!(
2602 "Invalid authority provided: {provided_current_authority:?}, expected \
2603 {permitted_authorities:?}"
2604 )))
2605 } else {
2606 Ok(())
2607 }
2608}
2609
2610pub async fn get_epoch_boundary_timestamps(
2611 rpc_client: &RpcClient,
2612 reward: &RpcInflationReward,
2613 epoch_schedule: &EpochSchedule,
2614) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
2615 let epoch_end_time = rpc_client.get_block_time(reward.effective_slot).await?;
2616 let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
2617 let epoch_start_time = loop {
2618 if epoch_start_slot >= reward.effective_slot {
2619 return Err("epoch_start_time not found".to_string().into());
2620 }
2621 match rpc_client.get_block_time(epoch_start_slot).await {
2622 Ok(block_time) => {
2623 break block_time;
2624 }
2625 Err(_) => {
2626 epoch_start_slot = epoch_start_slot
2630 .checked_add(1)
2631 .ok_or("Reached last slot that fits into u64")?;
2632 }
2633 }
2634 };
2635 Ok((epoch_start_time, epoch_end_time))
2636}
2637
2638pub fn make_cli_reward(
2639 reward: &RpcInflationReward,
2640 block_time: UnixTimestamp,
2641 epoch_start_time: UnixTimestamp,
2642 epoch_end_time: UnixTimestamp,
2643) -> Option<CliEpochReward> {
2644 let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
2645 if reward.post_balance > reward.amount {
2646 let rate_change =
2647 reward.amount as f64 / (reward.post_balance.saturating_sub(reward.amount)) as f64;
2648
2649 let wallclock_epochs_per_year =
2650 (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
2651 let apr = rate_change * wallclock_epochs_per_year;
2652
2653 Some(CliEpochReward {
2654 epoch: reward.epoch,
2655 effective_slot: reward.effective_slot,
2656 amount: reward.amount,
2657 post_balance: reward.post_balance,
2658 percent_change: rate_change * 100.0,
2659 apr: Some(apr * 100.0),
2660 commission: reward.commission,
2661 block_time,
2662 })
2663 } else {
2664 None
2665 }
2666}
2667
2668pub(crate) async fn fetch_epoch_rewards(
2669 rpc_client: &RpcClient,
2670 address: &Pubkey,
2671 mut num_epochs: usize,
2672 starting_epoch: Option<u64>,
2673) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
2674 let mut all_epoch_rewards = vec![];
2675 let epoch_schedule = rpc_client.get_epoch_schedule().await?;
2676 let mut rewards_epoch = if let Some(epoch) = starting_epoch {
2677 epoch
2678 } else {
2679 rpc_client
2680 .get_epoch_info()
2681 .await?
2682 .epoch
2683 .saturating_sub(num_epochs as u64)
2684 };
2685
2686 while num_epochs > 0 {
2687 if let Ok(rewards) = rpc_client
2688 .get_inflation_reward(&[*address], Some(rewards_epoch))
2689 .await
2690 {
2691 if let Some(reward) = &rewards[0] {
2692 let (epoch_start_time, epoch_end_time) =
2693 get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule).await?;
2694 let block_time = rpc_client.get_block_time(reward.effective_slot).await?;
2695 if let Some(cli_reward) =
2696 make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
2697 {
2698 all_epoch_rewards.push(cli_reward);
2699 }
2700 }
2701 } else {
2702 eprintln!("Rewards not available for epoch {rewards_epoch}");
2703 }
2704 num_epochs = num_epochs.saturating_sub(1);
2705 rewards_epoch = rewards_epoch.saturating_add(1);
2706 }
2707
2708 Ok(all_epoch_rewards)
2709}
2710
2711pub async fn process_show_stake_account(
2712 rpc_client: &RpcClient,
2713 config: &CliConfig<'_>,
2714 stake_account_address: &Pubkey,
2715 use_lamports_unit: bool,
2716 with_rewards: Option<usize>,
2717 use_csv: bool,
2718 starting_epoch: Option<u64>,
2719) -> ProcessResult {
2720 let stake_account = rpc_client.get_account(stake_account_address).await?;
2721 let state = get_account_stake_state(
2722 rpc_client,
2723 stake_account_address,
2724 stake_account,
2725 use_lamports_unit,
2726 with_rewards,
2727 use_csv,
2728 starting_epoch,
2729 )
2730 .await?;
2731 Ok(config.output_format.formatted_string(&state))
2732}
2733
2734pub async fn get_account_stake_state(
2735 rpc_client: &RpcClient,
2736 stake_account_address: &Pubkey,
2737 stake_account: solana_account::Account,
2738 use_lamports_unit: bool,
2739 with_rewards: Option<usize>,
2740 use_csv: bool,
2741 starting_epoch: Option<u64>,
2742) -> Result<CliStakeState, CliError> {
2743 if stake_account.owner != stake::program::id() {
2744 return Err(CliError::RpcRequestError(format!(
2745 "{stake_account_address:?} is not a stake account",
2746 )));
2747 }
2748 match stake_account.state() {
2749 Ok(stake_state) => {
2750 let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2751 let stake_history: StakeHistory = bincode::deserialize(&stake_history_account.data)
2752 .map_err(|_| {
2753 CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2754 })?;
2755 let clock_account = rpc_client.get_account(&clock::id()).await?;
2756 let clock: Clock = from_account(&clock_account).ok_or_else(|| {
2757 CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
2758 })?;
2759 let new_rate_activation_epoch = get_feature_activation_epoch(
2760 rpc_client,
2761 &agave_feature_set::reduce_stake_warmup_cooldown::id(),
2762 )
2763 .await?;
2764 let fixed_point_activation_epoch = get_feature_activation_epoch(
2765 rpc_client,
2766 &agave_feature_set::upgrade_bpf_stake_program_to_v5_1::id(),
2767 )
2768 .await?;
2769 let use_fixed_point_stake_math = fixed_point_activation_epoch
2770 .is_some_and(|activation_epoch| clock.epoch >= activation_epoch);
2771 let rent_exempt_balance = rpc_client
2772 .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2773 .await?;
2774 let mut state = build_stake_state(
2775 stake_account.lamports,
2776 &stake_state,
2777 use_lamports_unit,
2778 &stake_history,
2779 &clock,
2780 new_rate_activation_epoch,
2781 rent_exempt_balance,
2782 use_csv,
2783 use_fixed_point_stake_math,
2784 );
2785
2786 if state.stake_type == CliStakeType::Stake
2787 && state.activation_epoch.is_some()
2788 && let Some(num_epochs) = with_rewards
2789 {
2790 state.epoch_rewards = match fetch_epoch_rewards(
2791 rpc_client,
2792 stake_account_address,
2793 num_epochs,
2794 starting_epoch,
2795 )
2796 .await
2797 {
2798 Ok(rewards) => Some(rewards),
2799 Err(error) => {
2800 eprintln!("Failed to fetch epoch rewards: {error:?}");
2801 None
2802 }
2803 };
2804 }
2805 Ok(state)
2806 }
2807 Err(err) => Err(CliError::RpcRequestError(format!(
2808 "Account data could not be deserialized to stake state: {err}"
2809 ))),
2810 }
2811}
2812
2813pub async fn process_show_stake_history(
2814 rpc_client: &RpcClient,
2815 config: &CliConfig<'_>,
2816 use_lamports_unit: bool,
2817 limit_results: usize,
2818) -> ProcessResult {
2819 let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2820 let stake_history =
2821 bincode::deserialize::<StakeHistory>(&stake_history_account.data).map_err(|_| {
2822 CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2823 })?;
2824
2825 let limit_results = match config.output_format {
2826 OutputFormat::Json | OutputFormat::JsonCompact => usize::MAX,
2827 _ => {
2828 if limit_results == 0 {
2829 usize::MAX
2830 } else {
2831 limit_results
2832 }
2833 }
2834 };
2835 let mut entries: Vec<CliStakeHistoryEntry> = vec![];
2836 for entry in stake_history.deref().iter().take(limit_results) {
2837 entries.push(entry.into());
2838 }
2839 let stake_history_output = CliStakeHistory {
2840 entries,
2841 use_lamports_unit,
2842 };
2843 Ok(config.output_format.formatted_string(&stake_history_output))
2844}
2845
2846#[allow(clippy::too_many_arguments)]
2847pub async fn process_delegate_stake(
2848 rpc_client: &RpcClient,
2849 config: &CliConfig<'_>,
2850 stake_account_pubkey: &Pubkey,
2851 vote_account_pubkey: &Pubkey,
2852 stake_authority: SignerIndex,
2853 force: bool,
2854 sign_only: bool,
2855 dump_transaction_message: bool,
2856 blockhash_query: &BlockhashQuery,
2857 nonce_account: Option<Pubkey>,
2858 nonce_authority: SignerIndex,
2859 memo: Option<&String>,
2860 fee_payer: SignerIndex,
2861 compute_unit_price: Option<u64>,
2862) -> ProcessResult {
2863 check_unique_pubkeys(
2864 (&config.signers[0].pubkey(), "cli keypair".to_string()),
2865 (stake_account_pubkey, "stake_account_pubkey".to_string()),
2866 )?;
2867 let stake_authority = config.signers[stake_authority];
2868
2869 if !sign_only {
2870 let get_vote_accounts_config = RpcGetVoteAccountsConfig {
2873 vote_pubkey: Some(vote_account_pubkey.to_string()),
2874 keep_unstaked_delinquents: Some(true),
2875 commitment: Some(rpc_client.commitment()),
2876 ..RpcGetVoteAccountsConfig::default()
2877 };
2878 let RpcVoteAccountStatus {
2879 current,
2880 delinquent,
2881 } = rpc_client
2882 .get_vote_accounts_with_config(get_vote_accounts_config)
2883 .await?;
2884 let rpc_vote_account =
2886 current
2887 .first()
2888 .or_else(|| delinquent.first())
2889 .ok_or(CliError::RpcRequestError(format!(
2890 "Vote account not found: {vote_account_pubkey}"
2891 )))?;
2892
2893 let activated_stake = rpc_vote_account.activated_stake;
2894 let root_slot = rpc_vote_account.root_slot;
2895 let min_root_slot = rpc_client
2896 .get_slot()
2897 .await
2898 .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
2899 let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
2900 Ok(())
2901 } else if root_slot == 0 {
2902 Err(CliError::BadParameter(
2903 "Unable to delegate. Vote account has no root slot".to_string(),
2904 ))
2905 } else {
2906 Err(CliError::DynamicProgramError(format!(
2907 "Unable to delegate. Vote account appears delinquent because its current root \
2908 slot, {root_slot}, is less than {min_root_slot}"
2909 )))
2910 };
2911
2912 if let Err(err) = &sanity_check_result {
2913 if !force {
2914 sanity_check_result?;
2915 } else {
2916 println!("--force supplied, ignoring: {err}");
2917 }
2918 }
2919
2920 if !force {
2921 let stake_account = rpc_client.get_account(stake_account_pubkey).await?;
2922 if stake_account.owner != stake::program::id() {
2923 return Err(CliError::BadParameter(format!(
2924 "{stake_account_pubkey} is not a stake account",
2925 ))
2926 .into());
2927 }
2928
2929 let rent_exempt_balance = rpc_client
2930 .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2931 .await?;
2932 let minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2933 let minimum_total_lamports = rent_exempt_balance.saturating_add(minimum_delegation);
2934 let stake_account_lamports = stake_account.lamports;
2935
2936 if stake_account_lamports < minimum_total_lamports {
2937 return Err(CliError::BadParameter(format!(
2938 "need at least {minimum_total_lamports} lamports to delegate this stake \
2939 account, available lamports: {stake_account_lamports}"
2940 ))
2941 .into());
2942 }
2943 }
2944 }
2945
2946 let recent_blockhash = blockhash_query
2947 .get_blockhash(rpc_client, config.commitment)
2948 .await?;
2949
2950 let compute_unit_limit = match blockhash_query {
2952 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2953 BlockhashQuery::Rpc(_) => ComputeUnitLimit::SimulatedWithExtraPercentage(5),
2954 };
2955 let ixs = vec![stake_instruction::delegate_stake(
2956 stake_account_pubkey,
2957 &stake_authority.pubkey(),
2958 vote_account_pubkey,
2959 )]
2960 .with_memo(memo)
2961 .with_compute_unit_config(&ComputeUnitConfig {
2962 compute_unit_price,
2963 compute_unit_limit,
2964 });
2965
2966 let nonce_authority = config.signers[nonce_authority];
2967 let fee_payer = config.signers[fee_payer];
2968
2969 let mut message = if let Some(nonce_account) = &nonce_account {
2970 Message::new_with_nonce(
2971 ixs,
2972 Some(&fee_payer.pubkey()),
2973 nonce_account,
2974 &nonce_authority.pubkey(),
2975 )
2976 } else {
2977 Message::new(&ixs, Some(&fee_payer.pubkey()))
2978 };
2979 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2980 let mut tx = Transaction::new_unsigned(message);
2981
2982 if sign_only {
2983 tx.try_partial_sign(&config.signers, recent_blockhash)?;
2984 return_signers_with_config(
2985 &tx,
2986 &config.output_format,
2987 &ReturnSignersConfig {
2988 dump_transaction_message,
2989 },
2990 )
2991 } else {
2992 tx.try_sign(&config.signers, recent_blockhash)?;
2993 if let Some(nonce_account) = &nonce_account {
2994 let nonce_account =
2995 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2996 rpc_client,
2997 nonce_account,
2998 config.commitment,
2999 )
3000 .await?;
3001 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
3002 }
3003 check_account_for_fee_with_commitment(
3004 rpc_client,
3005 &tx.message.account_keys[0],
3006 &tx.message,
3007 config.commitment,
3008 )
3009 .await?;
3010 let result = rpc_client
3011 .send_and_confirm_transaction_with_spinner_and_config(
3012 &tx,
3013 config.commitment,
3014 config.send_transaction_config,
3015 )
3016 .await;
3017 log_instruction_custom_error::<StakeError>(result, config)
3018 }
3019}
3020
3021pub async fn process_stake_minimum_delegation(
3022 rpc_client: &RpcClient,
3023 config: &CliConfig<'_>,
3024 use_lamports_unit: bool,
3025) -> ProcessResult {
3026 let stake_minimum_delegation = rpc_client
3027 .get_stake_minimum_delegation_with_commitment(config.commitment)
3028 .await?;
3029
3030 let stake_minimum_delegation_output = CliBalance {
3031 lamports: stake_minimum_delegation,
3032 config: BuildBalanceMessageConfig {
3033 use_lamports_unit,
3034 show_unit: true,
3035 trim_trailing_zeros: true,
3036 },
3037 };
3038
3039 Ok(config
3040 .output_format
3041 .formatted_string(&stake_minimum_delegation_output))
3042}
3043
3044#[cfg(test)]
3045mod tests {
3046 use {
3047 super::*,
3048 crate::{clap_app::get_clap_app, cli::parse_command},
3049 solana_hash::Hash,
3050 solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair},
3051 solana_presigner::Presigner,
3052 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
3053 solana_signer::Signer,
3054 tempfile::NamedTempFile,
3055 };
3056
3057 fn make_tmp_file() -> (String, NamedTempFile) {
3058 let tmp_file = NamedTempFile::new().unwrap();
3059 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
3060 }
3061
3062 #[test]
3063 #[allow(clippy::cognitive_complexity)]
3064 fn test_parse_command() {
3065 let test_commands = get_clap_app("test", "desc", "version");
3066 let default_keypair = Keypair::new();
3067 let (default_keypair_file, mut tmp_file) = make_tmp_file();
3068 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
3069 let default_signer = DefaultSigner::new("", &default_keypair_file);
3070 let (keypair_file, mut tmp_file) = make_tmp_file();
3071 let stake_account_keypair = Keypair::new();
3072 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
3073 let stake_account_pubkey = stake_account_keypair.pubkey();
3074 let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
3075 let stake_authority_keypair = Keypair::new();
3076 write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
3077 let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
3078 let custodian_keypair = Keypair::new();
3079 write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
3080
3081 let stake_account_string = stake_account_pubkey.to_string();
3083 let new_stake_authority = Pubkey::from([1u8; 32]);
3084 let new_stake_string = new_stake_authority.to_string();
3085 let new_withdraw_authority = Pubkey::from([2u8; 32]);
3086 let new_withdraw_string = new_withdraw_authority.to_string();
3087 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3088 "test",
3089 "stake-authorize",
3090 &stake_account_string,
3091 "--new-stake-authority",
3092 &new_stake_string,
3093 "--new-withdraw-authority",
3094 &new_withdraw_string,
3095 ]);
3096 assert_eq!(
3097 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3098 CliCommandInfo {
3099 command: CliCommand::StakeAuthorize {
3100 stake_account_pubkey,
3101 new_authorizations: vec![
3102 StakeAuthorizationIndexed {
3103 authorization_type: StakeAuthorize::Staker,
3104 new_authority_pubkey: new_stake_authority,
3105 authority: 0,
3106 new_authority_signer: None,
3107 },
3108 StakeAuthorizationIndexed {
3109 authorization_type: StakeAuthorize::Withdrawer,
3110 new_authority_pubkey: new_withdraw_authority,
3111 authority: 0,
3112 new_authority_signer: None,
3113 },
3114 ],
3115 sign_only: false,
3116 dump_transaction_message: false,
3117 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3118 nonce_account: None,
3119 nonce_authority: 0,
3120 memo: None,
3121 fee_payer: 0,
3122 custodian: None,
3123 no_wait: false,
3124 compute_unit_price: None,
3125 },
3126 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3127 },
3128 );
3129 let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3130 let withdraw_authority_keypair = Keypair::new();
3131 write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3132 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3133 "test",
3134 "stake-authorize",
3135 &stake_account_string,
3136 "--new-stake-authority",
3137 &new_stake_string,
3138 "--new-withdraw-authority",
3139 &new_withdraw_string,
3140 "--stake-authority",
3141 &stake_authority_keypair_file,
3142 "--withdraw-authority",
3143 &withdraw_authority_keypair_file,
3144 ]);
3145 assert_eq!(
3146 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3147 CliCommandInfo {
3148 command: CliCommand::StakeAuthorize {
3149 stake_account_pubkey,
3150 new_authorizations: vec![
3151 StakeAuthorizationIndexed {
3152 authorization_type: StakeAuthorize::Staker,
3153 new_authority_pubkey: new_stake_authority,
3154 authority: 1,
3155 new_authority_signer: None,
3156 },
3157 StakeAuthorizationIndexed {
3158 authorization_type: StakeAuthorize::Withdrawer,
3159 new_authority_pubkey: new_withdraw_authority,
3160 authority: 2,
3161 new_authority_signer: None,
3162 },
3163 ],
3164 sign_only: false,
3165 dump_transaction_message: false,
3166 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3167 nonce_account: None,
3168 nonce_authority: 0,
3169 memo: None,
3170 fee_payer: 0,
3171 custodian: None,
3172 no_wait: false,
3173 compute_unit_price: None,
3174 },
3175 signers: vec![
3176 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3177 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3178 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3179 ],
3180 },
3181 );
3182 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3184 "test",
3185 "stake-authorize",
3186 &stake_account_string,
3187 "--new-stake-authority",
3188 &new_stake_string,
3189 "--new-withdraw-authority",
3190 &new_withdraw_string,
3191 "--withdraw-authority",
3192 &withdraw_authority_keypair_file,
3193 ]);
3194 assert_eq!(
3195 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3196 CliCommandInfo {
3197 command: CliCommand::StakeAuthorize {
3198 stake_account_pubkey,
3199 new_authorizations: vec![
3200 StakeAuthorizationIndexed {
3201 authorization_type: StakeAuthorize::Staker,
3202 new_authority_pubkey: new_stake_authority,
3203 authority: 1,
3204 new_authority_signer: None,
3205 },
3206 StakeAuthorizationIndexed {
3207 authorization_type: StakeAuthorize::Withdrawer,
3208 new_authority_pubkey: new_withdraw_authority,
3209 authority: 1,
3210 new_authority_signer: None,
3211 },
3212 ],
3213 sign_only: false,
3214 dump_transaction_message: false,
3215 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3216 nonce_account: None,
3217 nonce_authority: 0,
3218 memo: None,
3219 fee_payer: 0,
3220 custodian: None,
3221 no_wait: false,
3222 compute_unit_price: None,
3223 },
3224 signers: vec![
3225 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3226 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3227 ],
3228 },
3229 );
3230 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3231 "test",
3232 "stake-authorize",
3233 &stake_account_string,
3234 "--new-stake-authority",
3235 &new_stake_string,
3236 ]);
3237 assert_eq!(
3238 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3239 CliCommandInfo {
3240 command: CliCommand::StakeAuthorize {
3241 stake_account_pubkey,
3242 new_authorizations: vec![StakeAuthorizationIndexed {
3243 authorization_type: StakeAuthorize::Staker,
3244 new_authority_pubkey: new_stake_authority,
3245 authority: 0,
3246 new_authority_signer: None,
3247 }],
3248 sign_only: false,
3249 dump_transaction_message: false,
3250 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3251 nonce_account: None,
3252 nonce_authority: 0,
3253 memo: None,
3254 fee_payer: 0,
3255 custodian: None,
3256 no_wait: false,
3257 compute_unit_price: None,
3258 },
3259 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3260 },
3261 );
3262 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3263 "test",
3264 "stake-authorize",
3265 &stake_account_string,
3266 "--new-stake-authority",
3267 &new_stake_string,
3268 "--stake-authority",
3269 &stake_authority_keypair_file,
3270 ]);
3271 assert_eq!(
3272 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3273 CliCommandInfo {
3274 command: CliCommand::StakeAuthorize {
3275 stake_account_pubkey,
3276 new_authorizations: vec![StakeAuthorizationIndexed {
3277 authorization_type: StakeAuthorize::Staker,
3278 new_authority_pubkey: new_stake_authority,
3279 authority: 1,
3280 new_authority_signer: None,
3281 }],
3282 sign_only: false,
3283 dump_transaction_message: false,
3284 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3285 nonce_account: None,
3286 nonce_authority: 0,
3287 memo: None,
3288 fee_payer: 0,
3289 custodian: None,
3290 no_wait: false,
3291 compute_unit_price: None,
3292 },
3293 signers: vec![
3294 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3295 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3296 ],
3297 },
3298 );
3299 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3301 "test",
3302 "stake-authorize",
3303 &stake_account_string,
3304 "--new-stake-authority",
3305 &new_stake_string,
3306 "--withdraw-authority",
3307 &withdraw_authority_keypair_file,
3308 ]);
3309 assert_eq!(
3310 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3311 CliCommandInfo {
3312 command: CliCommand::StakeAuthorize {
3313 stake_account_pubkey,
3314 new_authorizations: vec![StakeAuthorizationIndexed {
3315 authorization_type: StakeAuthorize::Staker,
3316 new_authority_pubkey: new_stake_authority,
3317 authority: 1,
3318 new_authority_signer: None,
3319 }],
3320 sign_only: false,
3321 dump_transaction_message: false,
3322 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3323 nonce_account: None,
3324 nonce_authority: 0,
3325 memo: None,
3326 fee_payer: 0,
3327 custodian: None,
3328 no_wait: false,
3329 compute_unit_price: None,
3330 },
3331 signers: vec![
3332 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3333 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3334 ],
3335 },
3336 );
3337 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3338 "test",
3339 "stake-authorize",
3340 &stake_account_string,
3341 "--new-withdraw-authority",
3342 &new_withdraw_string,
3343 ]);
3344 assert_eq!(
3345 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3346 CliCommandInfo {
3347 command: CliCommand::StakeAuthorize {
3348 stake_account_pubkey,
3349 new_authorizations: vec![StakeAuthorizationIndexed {
3350 authorization_type: StakeAuthorize::Withdrawer,
3351 new_authority_pubkey: new_withdraw_authority,
3352 authority: 0,
3353 new_authority_signer: None,
3354 }],
3355 sign_only: false,
3356 dump_transaction_message: false,
3357 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3358 nonce_account: None,
3359 nonce_authority: 0,
3360 memo: None,
3361 fee_payer: 0,
3362 custodian: None,
3363 no_wait: false,
3364 compute_unit_price: None,
3365 },
3366 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3367 },
3368 );
3369 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3370 "test",
3371 "stake-authorize",
3372 &stake_account_string,
3373 "--new-withdraw-authority",
3374 &new_withdraw_string,
3375 "--withdraw-authority",
3376 &withdraw_authority_keypair_file,
3377 ]);
3378 assert_eq!(
3379 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3380 CliCommandInfo {
3381 command: CliCommand::StakeAuthorize {
3382 stake_account_pubkey,
3383 new_authorizations: vec![StakeAuthorizationIndexed {
3384 authorization_type: StakeAuthorize::Withdrawer,
3385 new_authority_pubkey: new_withdraw_authority,
3386 authority: 1,
3387 new_authority_signer: None,
3388 }],
3389 sign_only: false,
3390 dump_transaction_message: false,
3391 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3392 nonce_account: None,
3393 nonce_authority: 0,
3394 memo: None,
3395 fee_payer: 0,
3396 custodian: None,
3397 no_wait: false,
3398 compute_unit_price: None,
3399 },
3400 signers: vec![
3401 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3402 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3403 ],
3404 },
3405 );
3406
3407 let test_authorize = test_commands.clone().get_matches_from(vec![
3409 "test",
3410 "stake-authorize",
3411 &stake_account_string,
3412 "--new-stake-authority",
3413 &stake_account_string,
3414 "--no-wait",
3415 ]);
3416 assert_eq!(
3417 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3418 CliCommandInfo {
3419 command: CliCommand::StakeAuthorize {
3420 stake_account_pubkey,
3421 new_authorizations: vec![StakeAuthorizationIndexed {
3422 authorization_type: StakeAuthorize::Staker,
3423 new_authority_pubkey: stake_account_pubkey,
3424 authority: 0,
3425 new_authority_signer: None,
3426 }],
3427 sign_only: false,
3428 dump_transaction_message: false,
3429 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3430 nonce_account: None,
3431 nonce_authority: 0,
3432 memo: None,
3433 fee_payer: 0,
3434 custodian: None,
3435 no_wait: true,
3436 compute_unit_price: None,
3437 },
3438 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3439 }
3440 );
3441
3442 let (authority_keypair_file, mut tmp_file) = make_tmp_file();
3444 let authority_keypair = Keypair::new();
3445 write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
3446 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3447 "test",
3448 "stake-authorize-checked",
3449 &stake_account_string,
3450 "--new-stake-authority",
3451 &authority_keypair_file,
3452 "--new-withdraw-authority",
3453 &authority_keypair_file,
3454 ]);
3455 assert_eq!(
3456 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3457 CliCommandInfo {
3458 command: CliCommand::StakeAuthorize {
3459 stake_account_pubkey,
3460 new_authorizations: vec![
3461 StakeAuthorizationIndexed {
3462 authorization_type: StakeAuthorize::Staker,
3463 new_authority_pubkey: authority_keypair.pubkey(),
3464 authority: 0,
3465 new_authority_signer: Some(1),
3466 },
3467 StakeAuthorizationIndexed {
3468 authorization_type: StakeAuthorize::Withdrawer,
3469 new_authority_pubkey: authority_keypair.pubkey(),
3470 authority: 0,
3471 new_authority_signer: Some(1),
3472 },
3473 ],
3474 sign_only: false,
3475 dump_transaction_message: false,
3476 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3477 nonce_account: None,
3478 nonce_authority: 0,
3479 memo: None,
3480 fee_payer: 0,
3481 custodian: None,
3482 no_wait: false,
3483 compute_unit_price: None,
3484 },
3485 signers: vec![
3486 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3487 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3488 ],
3489 },
3490 );
3491 let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3492 let withdraw_authority_keypair = Keypair::new();
3493 write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3494 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3495 "test",
3496 "stake-authorize-checked",
3497 &stake_account_string,
3498 "--new-stake-authority",
3499 &authority_keypair_file,
3500 "--new-withdraw-authority",
3501 &authority_keypair_file,
3502 "--stake-authority",
3503 &stake_authority_keypair_file,
3504 "--withdraw-authority",
3505 &withdraw_authority_keypair_file,
3506 ]);
3507 assert_eq!(
3508 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3509 CliCommandInfo {
3510 command: CliCommand::StakeAuthorize {
3511 stake_account_pubkey,
3512 new_authorizations: vec![
3513 StakeAuthorizationIndexed {
3514 authorization_type: StakeAuthorize::Staker,
3515 new_authority_pubkey: authority_keypair.pubkey(),
3516 authority: 1,
3517 new_authority_signer: Some(2),
3518 },
3519 StakeAuthorizationIndexed {
3520 authorization_type: StakeAuthorize::Withdrawer,
3521 new_authority_pubkey: authority_keypair.pubkey(),
3522 authority: 3,
3523 new_authority_signer: Some(2),
3524 },
3525 ],
3526 sign_only: false,
3527 dump_transaction_message: false,
3528 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3529 nonce_account: None,
3530 nonce_authority: 0,
3531 memo: None,
3532 fee_payer: 0,
3533 custodian: None,
3534 no_wait: false,
3535 compute_unit_price: None,
3536 },
3537 signers: vec![
3538 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3539 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3540 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3541 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3542 ],
3543 },
3544 );
3545 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3547 "test",
3548 "stake-authorize-checked",
3549 &stake_account_string,
3550 "--new-stake-authority",
3551 &authority_keypair_file,
3552 "--new-withdraw-authority",
3553 &authority_keypair_file,
3554 "--withdraw-authority",
3555 &withdraw_authority_keypair_file,
3556 ]);
3557 assert_eq!(
3558 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3559 CliCommandInfo {
3560 command: CliCommand::StakeAuthorize {
3561 stake_account_pubkey,
3562 new_authorizations: vec![
3563 StakeAuthorizationIndexed {
3564 authorization_type: StakeAuthorize::Staker,
3565 new_authority_pubkey: authority_keypair.pubkey(),
3566 authority: 1,
3567 new_authority_signer: Some(2),
3568 },
3569 StakeAuthorizationIndexed {
3570 authorization_type: StakeAuthorize::Withdrawer,
3571 new_authority_pubkey: authority_keypair.pubkey(),
3572 authority: 1,
3573 new_authority_signer: Some(2),
3574 },
3575 ],
3576 sign_only: false,
3577 dump_transaction_message: false,
3578 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3579 nonce_account: None,
3580 nonce_authority: 0,
3581 memo: None,
3582 fee_payer: 0,
3583 custodian: None,
3584 no_wait: false,
3585 compute_unit_price: None,
3586 },
3587 signers: vec![
3588 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3589 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3590 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3591 ],
3592 },
3593 );
3594 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3595 "test",
3596 "stake-authorize-checked",
3597 &stake_account_string,
3598 "--new-stake-authority",
3599 &authority_keypair_file,
3600 ]);
3601 assert_eq!(
3602 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3603 CliCommandInfo {
3604 command: CliCommand::StakeAuthorize {
3605 stake_account_pubkey,
3606 new_authorizations: vec![StakeAuthorizationIndexed {
3607 authorization_type: StakeAuthorize::Staker,
3608 new_authority_pubkey: authority_keypair.pubkey(),
3609 authority: 0,
3610 new_authority_signer: Some(1),
3611 }],
3612 sign_only: false,
3613 dump_transaction_message: false,
3614 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3615 nonce_account: None,
3616 nonce_authority: 0,
3617 memo: None,
3618 fee_payer: 0,
3619 custodian: None,
3620 no_wait: false,
3621 compute_unit_price: None,
3622 },
3623 signers: vec![
3624 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3625 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3626 ],
3627 },
3628 );
3629 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3630 "test",
3631 "stake-authorize-checked",
3632 &stake_account_string,
3633 "--new-stake-authority",
3634 &authority_keypair_file,
3635 "--stake-authority",
3636 &stake_authority_keypair_file,
3637 ]);
3638 assert_eq!(
3639 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3640 CliCommandInfo {
3641 command: CliCommand::StakeAuthorize {
3642 stake_account_pubkey,
3643 new_authorizations: vec![StakeAuthorizationIndexed {
3644 authorization_type: StakeAuthorize::Staker,
3645 new_authority_pubkey: authority_keypair.pubkey(),
3646 authority: 1,
3647 new_authority_signer: Some(2),
3648 }],
3649 sign_only: false,
3650 dump_transaction_message: false,
3651 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3652 nonce_account: None,
3653 nonce_authority: 0,
3654 memo: None,
3655 fee_payer: 0,
3656 custodian: None,
3657 no_wait: false,
3658 compute_unit_price: None,
3659 },
3660 signers: vec![
3661 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3662 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3663 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3664 ],
3665 },
3666 );
3667 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3669 "test",
3670 "stake-authorize-checked",
3671 &stake_account_string,
3672 "--new-stake-authority",
3673 &authority_keypair_file,
3674 "--withdraw-authority",
3675 &withdraw_authority_keypair_file,
3676 ]);
3677 assert_eq!(
3678 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3679 CliCommandInfo {
3680 command: CliCommand::StakeAuthorize {
3681 stake_account_pubkey,
3682 new_authorizations: vec![StakeAuthorizationIndexed {
3683 authorization_type: StakeAuthorize::Staker,
3684 new_authority_pubkey: authority_keypair.pubkey(),
3685 authority: 1,
3686 new_authority_signer: Some(2),
3687 }],
3688 sign_only: false,
3689 dump_transaction_message: false,
3690 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3691 nonce_account: None,
3692 nonce_authority: 0,
3693 memo: None,
3694 fee_payer: 0,
3695 custodian: None,
3696 no_wait: false,
3697 compute_unit_price: None,
3698 },
3699 signers: vec![
3700 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3701 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3702 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3703 ],
3704 },
3705 );
3706 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3707 "test",
3708 "stake-authorize-checked",
3709 &stake_account_string,
3710 "--new-withdraw-authority",
3711 &authority_keypair_file,
3712 ]);
3713 assert_eq!(
3714 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3715 CliCommandInfo {
3716 command: CliCommand::StakeAuthorize {
3717 stake_account_pubkey,
3718 new_authorizations: vec![StakeAuthorizationIndexed {
3719 authorization_type: StakeAuthorize::Withdrawer,
3720 new_authority_pubkey: authority_keypair.pubkey(),
3721 authority: 0,
3722 new_authority_signer: Some(1),
3723 }],
3724 sign_only: false,
3725 dump_transaction_message: false,
3726 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3727 nonce_account: None,
3728 nonce_authority: 0,
3729 memo: None,
3730 fee_payer: 0,
3731 custodian: None,
3732 no_wait: false,
3733 compute_unit_price: None,
3734 },
3735 signers: vec![
3736 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3737 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3738 ],
3739 },
3740 );
3741 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3742 "test",
3743 "stake-authorize-checked",
3744 &stake_account_string,
3745 "--new-withdraw-authority",
3746 &authority_keypair_file,
3747 "--withdraw-authority",
3748 &withdraw_authority_keypair_file,
3749 ]);
3750 assert_eq!(
3751 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3752 CliCommandInfo {
3753 command: CliCommand::StakeAuthorize {
3754 stake_account_pubkey,
3755 new_authorizations: vec![StakeAuthorizationIndexed {
3756 authorization_type: StakeAuthorize::Withdrawer,
3757 new_authority_pubkey: authority_keypair.pubkey(),
3758 authority: 1,
3759 new_authority_signer: Some(2),
3760 }],
3761 sign_only: false,
3762 dump_transaction_message: false,
3763 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3764 nonce_account: None,
3765 nonce_authority: 0,
3766 memo: None,
3767 fee_payer: 0,
3768 custodian: None,
3769 no_wait: false,
3770 compute_unit_price: None,
3771 },
3772 signers: vec![
3773 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3774 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3775 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3776 ],
3777 },
3778 );
3779
3780 let test_authorize = test_commands.clone().get_matches_from(vec![
3782 "test",
3783 "stake-authorize-checked",
3784 &stake_account_string,
3785 "--new-stake-authority",
3786 &authority_keypair_file,
3787 "--no-wait",
3788 ]);
3789 assert_eq!(
3790 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3791 CliCommandInfo {
3792 command: CliCommand::StakeAuthorize {
3793 stake_account_pubkey,
3794 new_authorizations: vec![StakeAuthorizationIndexed {
3795 authorization_type: StakeAuthorize::Staker,
3796 new_authority_pubkey: authority_keypair.pubkey(),
3797 authority: 0,
3798 new_authority_signer: Some(1),
3799 }],
3800 sign_only: false,
3801 dump_transaction_message: false,
3802 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3803 nonce_account: None,
3804 nonce_authority: 0,
3805 memo: None,
3806 fee_payer: 0,
3807 custodian: None,
3808 no_wait: true,
3809 compute_unit_price: None,
3810 },
3811 signers: vec![
3812 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3813 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3814 ],
3815 }
3816 );
3817
3818 let blockhash = Hash::default();
3820 let blockhash_string = format!("{blockhash}");
3821 let test_authorize = test_commands.clone().get_matches_from(vec![
3822 "test",
3823 "stake-authorize",
3824 &stake_account_string,
3825 "--new-stake-authority",
3826 &stake_account_string,
3827 "--blockhash",
3828 &blockhash_string,
3829 "--sign-only",
3830 ]);
3831 assert_eq!(
3832 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3833 CliCommandInfo {
3834 command: CliCommand::StakeAuthorize {
3835 stake_account_pubkey,
3836 new_authorizations: vec![StakeAuthorizationIndexed {
3837 authorization_type: StakeAuthorize::Staker,
3838 new_authority_pubkey: stake_account_pubkey,
3839 authority: 0,
3840 new_authority_signer: None,
3841 }],
3842 sign_only: true,
3843 dump_transaction_message: false,
3844 blockhash_query: BlockhashQuery::Static(blockhash),
3845 nonce_account: None,
3846 nonce_authority: 0,
3847 memo: None,
3848 fee_payer: 0,
3849 custodian: None,
3850 no_wait: false,
3851 compute_unit_price: None,
3852 },
3853 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3854 }
3855 );
3856 let keypair = Keypair::new();
3858 let pubkey = keypair.pubkey();
3859 let sig = keypair.sign_message(&[0u8]);
3860 let signer = format!("{}={}", keypair.pubkey(), sig);
3861 let test_authorize = test_commands.clone().get_matches_from(vec![
3862 "test",
3863 "stake-authorize",
3864 &stake_account_string,
3865 "--new-stake-authority",
3866 &stake_account_string,
3867 "--blockhash",
3868 &blockhash_string,
3869 "--signer",
3870 &signer,
3871 "--fee-payer",
3872 &pubkey.to_string(),
3873 ]);
3874 assert_eq!(
3875 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3876 CliCommandInfo {
3877 command: CliCommand::StakeAuthorize {
3878 stake_account_pubkey,
3879 new_authorizations: vec![StakeAuthorizationIndexed {
3880 authorization_type: StakeAuthorize::Staker,
3881 new_authority_pubkey: stake_account_pubkey,
3882 authority: 0,
3883 new_authority_signer: None,
3884 }],
3885 sign_only: false,
3886 dump_transaction_message: false,
3887 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3888 nonce_account: None,
3889 nonce_authority: 0,
3890 memo: None,
3891 fee_payer: 1,
3892 custodian: None,
3893 no_wait: false,
3894 compute_unit_price: None,
3895 },
3896 signers: vec![
3897 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3898 Box::new(Presigner::new(&pubkey, &sig))
3899 ],
3900 }
3901 );
3902 let keypair2 = Keypair::new();
3904 let pubkey2 = keypair2.pubkey();
3905 let sig2 = keypair.sign_message(&[0u8]);
3906 let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
3907 let nonce_account = Pubkey::from([1u8; 32]);
3908 let test_authorize = test_commands.clone().get_matches_from(vec![
3909 "test",
3910 "stake-authorize",
3911 &stake_account_string,
3912 "--new-stake-authority",
3913 &stake_account_string,
3914 "--blockhash",
3915 &blockhash_string,
3916 "--signer",
3917 &signer,
3918 "--signer",
3919 &signer2,
3920 "--fee-payer",
3921 &pubkey.to_string(),
3922 "--nonce",
3923 &nonce_account.to_string(),
3924 "--nonce-authority",
3925 &pubkey2.to_string(),
3926 ]);
3927 assert_eq!(
3928 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3929 CliCommandInfo {
3930 command: CliCommand::StakeAuthorize {
3931 stake_account_pubkey,
3932 new_authorizations: vec![StakeAuthorizationIndexed {
3933 authorization_type: StakeAuthorize::Staker,
3934 new_authority_pubkey: stake_account_pubkey,
3935 authority: 0,
3936 new_authority_signer: None,
3937 }],
3938 sign_only: false,
3939 dump_transaction_message: false,
3940 blockhash_query: BlockhashQuery::Validated(
3941 Source::NonceAccount(nonce_account),
3942 blockhash
3943 ),
3944 nonce_account: Some(nonce_account),
3945 nonce_authority: 2,
3946 memo: None,
3947 fee_payer: 1,
3948 custodian: None,
3949 no_wait: false,
3950 compute_unit_price: None,
3951 },
3952 signers: vec![
3953 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3954 Box::new(Presigner::new(&pubkey, &sig)),
3955 Box::new(Presigner::new(&pubkey2, &sig2)),
3956 ],
3957 }
3958 );
3959 let test_authorize = test_commands.clone().get_matches_from(vec![
3961 "test",
3962 "stake-authorize",
3963 &stake_account_string,
3964 "--new-stake-authority",
3965 &stake_account_string,
3966 "--blockhash",
3967 &blockhash_string,
3968 ]);
3969 assert_eq!(
3970 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3971 CliCommandInfo {
3972 command: CliCommand::StakeAuthorize {
3973 stake_account_pubkey,
3974 new_authorizations: vec![StakeAuthorizationIndexed {
3975 authorization_type: StakeAuthorize::Staker,
3976 new_authority_pubkey: stake_account_pubkey,
3977 authority: 0,
3978 new_authority_signer: None,
3979 }],
3980 sign_only: false,
3981 dump_transaction_message: false,
3982 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3983 nonce_account: None,
3984 nonce_authority: 0,
3985 memo: None,
3986 fee_payer: 0,
3987 custodian: None,
3988 no_wait: false,
3989 compute_unit_price: None,
3990 },
3991 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3992 }
3993 );
3994 let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
3996 let nonce_authority_keypair = Keypair::new();
3997 write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
3998 let nonce_account_pubkey = nonce_authority_keypair.pubkey();
3999 let nonce_account_string = nonce_account_pubkey.to_string();
4000 let test_authorize = test_commands.clone().get_matches_from(vec![
4001 "test",
4002 "stake-authorize",
4003 &stake_account_string,
4004 "--new-stake-authority",
4005 &stake_account_string,
4006 "--blockhash",
4007 &blockhash_string,
4008 "--nonce",
4009 &nonce_account_string,
4010 "--nonce-authority",
4011 &nonce_keypair_file,
4012 ]);
4013 assert_eq!(
4014 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4015 CliCommandInfo {
4016 command: CliCommand::StakeAuthorize {
4017 stake_account_pubkey,
4018 new_authorizations: vec![StakeAuthorizationIndexed {
4019 authorization_type: StakeAuthorize::Staker,
4020 new_authority_pubkey: stake_account_pubkey,
4021 authority: 0,
4022 new_authority_signer: None,
4023 }],
4024 sign_only: false,
4025 dump_transaction_message: false,
4026 blockhash_query: BlockhashQuery::Validated(
4027 Source::NonceAccount(nonce_account_pubkey),
4028 blockhash
4029 ),
4030 nonce_account: Some(nonce_account_pubkey),
4031 nonce_authority: 1,
4032 memo: None,
4033 fee_payer: 0,
4034 custodian: None,
4035 no_wait: false,
4036 compute_unit_price: None,
4037 },
4038 signers: vec![
4039 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4040 Box::new(nonce_authority_keypair)
4041 ],
4042 }
4043 );
4044 let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4046 let fee_payer_keypair = Keypair::new();
4047 write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4048 let fee_payer_pubkey = fee_payer_keypair.pubkey();
4049 let fee_payer_string = fee_payer_pubkey.to_string();
4050 let test_authorize = test_commands.clone().get_matches_from(vec![
4051 "test",
4052 "stake-authorize",
4053 &stake_account_string,
4054 "--new-stake-authority",
4055 &stake_account_string,
4056 "--fee-payer",
4057 &fee_payer_keypair_file,
4058 ]);
4059 assert_eq!(
4060 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4061 CliCommandInfo {
4062 command: CliCommand::StakeAuthorize {
4063 stake_account_pubkey,
4064 new_authorizations: vec![StakeAuthorizationIndexed {
4065 authorization_type: StakeAuthorize::Staker,
4066 new_authority_pubkey: stake_account_pubkey,
4067 authority: 0,
4068 new_authority_signer: None,
4069 }],
4070 sign_only: false,
4071 dump_transaction_message: false,
4072 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4073 nonce_account: None,
4074 nonce_authority: 0,
4075 memo: None,
4076 fee_payer: 1,
4077 custodian: None,
4078 no_wait: false,
4079 compute_unit_price: None,
4080 },
4081 signers: vec![
4082 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4083 Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap()),
4084 ],
4085 }
4086 );
4087 let sig = fee_payer_keypair.sign_message(&[0u8]);
4089 let signer = format!("{fee_payer_string}={sig}");
4090 let test_authorize = test_commands.clone().get_matches_from(vec![
4091 "test",
4092 "stake-authorize",
4093 &stake_account_string,
4094 "--new-stake-authority",
4095 &stake_account_string,
4096 "--fee-payer",
4097 &fee_payer_string,
4098 "--blockhash",
4099 &blockhash_string,
4100 "--signer",
4101 &signer,
4102 ]);
4103 assert_eq!(
4104 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4105 CliCommandInfo {
4106 command: CliCommand::StakeAuthorize {
4107 stake_account_pubkey,
4108 new_authorizations: vec![StakeAuthorizationIndexed {
4109 authorization_type: StakeAuthorize::Staker,
4110 new_authority_pubkey: stake_account_pubkey,
4111 authority: 0,
4112 new_authority_signer: None,
4113 }],
4114 sign_only: false,
4115 dump_transaction_message: false,
4116 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4117 nonce_account: None,
4118 nonce_authority: 0,
4119 memo: None,
4120 fee_payer: 1,
4121 custodian: None,
4122 no_wait: false,
4123 compute_unit_price: None,
4124 },
4125 signers: vec![
4126 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4127 Box::new(Presigner::new(&fee_payer_pubkey, &sig))
4128 ],
4129 }
4130 );
4131
4132 let custodian = solana_pubkey::new_rand();
4134 let custodian_string = format!("{custodian}");
4135 let authorized = solana_pubkey::new_rand();
4136 let authorized_string = format!("{authorized}");
4137 let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4138 "test",
4139 "create-stake-account",
4140 &keypair_file,
4141 "50",
4142 "--stake-authority",
4143 &authorized_string,
4144 "--withdraw-authority",
4145 &authorized_string,
4146 "--custodian",
4147 &custodian_string,
4148 "--lockup-epoch",
4149 "43",
4150 ]);
4151 assert_eq!(
4152 parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4153 CliCommandInfo {
4154 command: CliCommand::CreateStakeAccount {
4155 stake_account: 1,
4156 seed: None,
4157 staker: Some(authorized),
4158 withdrawer: Some(authorized),
4159 withdrawer_signer: None,
4160 lockup: Lockup {
4161 epoch: 43,
4162 unix_timestamp: 0,
4163 custodian,
4164 },
4165 amount: SpendAmount::Some(50_000_000_000),
4166 sign_only: false,
4167 dump_transaction_message: false,
4168 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4169 nonce_account: None,
4170 nonce_authority: 0,
4171 memo: None,
4172 fee_payer: 0,
4173 from: 0,
4174 compute_unit_price: None,
4175 },
4176 signers: vec![
4177 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4178 Box::new(stake_account_keypair)
4179 ],
4180 }
4181 );
4182
4183 let (keypair_file, mut tmp_file) = make_tmp_file();
4184 let stake_account_keypair = Keypair::new();
4185 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4186 let stake_account_pubkey = stake_account_keypair.pubkey();
4187 let stake_account_string = stake_account_pubkey.to_string();
4188
4189 let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4190 "test",
4191 "create-stake-account",
4192 &keypair_file,
4193 "50",
4194 ]);
4195
4196 assert_eq!(
4197 parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4198 CliCommandInfo {
4199 command: CliCommand::CreateStakeAccount {
4200 stake_account: 1,
4201 seed: None,
4202 staker: None,
4203 withdrawer: None,
4204 withdrawer_signer: None,
4205 lockup: Lockup::default(),
4206 amount: SpendAmount::Some(50_000_000_000),
4207 sign_only: false,
4208 dump_transaction_message: false,
4209 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4210 nonce_account: None,
4211 nonce_authority: 0,
4212 memo: None,
4213 fee_payer: 0,
4214 from: 0,
4215 compute_unit_price: None,
4216 },
4217 signers: vec![
4218 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4219 Box::new(read_keypair_file(&keypair_file).unwrap())
4220 ],
4221 }
4222 );
4223 let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
4224 let withdrawer_keypair = Keypair::new();
4225 write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
4226 let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4227 "test",
4228 "create-stake-account-checked",
4229 &keypair_file,
4230 "50",
4231 "--stake-authority",
4232 &authorized_string,
4233 "--withdraw-authority",
4234 &withdrawer_keypair_file,
4235 ]);
4236 assert_eq!(
4237 parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4238 CliCommandInfo {
4239 command: CliCommand::CreateStakeAccount {
4240 stake_account: 1,
4241 seed: None,
4242 staker: Some(authorized),
4243 withdrawer: Some(withdrawer_keypair.pubkey()),
4244 withdrawer_signer: Some(2),
4245 lockup: Lockup::default(),
4246 amount: SpendAmount::Some(50_000_000_000),
4247 sign_only: false,
4248 dump_transaction_message: false,
4249 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4250 nonce_account: None,
4251 nonce_authority: 0,
4252 memo: None,
4253 fee_payer: 0,
4254 from: 0,
4255 compute_unit_price: None,
4256 },
4257 signers: vec![
4258 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4259 Box::new(stake_account_keypair),
4260 Box::new(withdrawer_keypair),
4261 ],
4262 }
4263 );
4264
4265 let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4266 "test",
4267 "create-stake-account-checked",
4268 &keypair_file,
4269 "50",
4270 "--stake-authority",
4271 &authorized_string,
4272 "--withdraw-authority",
4273 &authorized_string,
4274 ]);
4275 assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());
4276
4277 let nonce_account = Pubkey::from([1u8; 32]);
4279 let nonce_account_string = nonce_account.to_string();
4280 let offline = keypair_from_seed(&[2u8; 32]).unwrap();
4281 let offline_pubkey = offline.pubkey();
4282 let offline_string = offline_pubkey.to_string();
4283 let offline_sig = offline.sign_message(&[3u8]);
4284 let offline_signer = format!("{offline_pubkey}={offline_sig}");
4285 let nonce_hash = Hash::new_from_array([4u8; 32]);
4286 let nonce_hash_string = nonce_hash.to_string();
4287 let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4288 "test",
4289 "create-stake-account",
4290 &keypair_file,
4291 "50",
4292 "--blockhash",
4293 &nonce_hash_string,
4294 "--nonce",
4295 &nonce_account_string,
4296 "--nonce-authority",
4297 &offline_string,
4298 "--fee-payer",
4299 &offline_string,
4300 "--from",
4301 &offline_string,
4302 "--signer",
4303 &offline_signer,
4304 ]);
4305
4306 assert_eq!(
4307 parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4308 CliCommandInfo {
4309 command: CliCommand::CreateStakeAccount {
4310 stake_account: 1,
4311 seed: None,
4312 staker: None,
4313 withdrawer: None,
4314 withdrawer_signer: None,
4315 lockup: Lockup::default(),
4316 amount: SpendAmount::Some(50_000_000_000),
4317 sign_only: false,
4318 dump_transaction_message: false,
4319 blockhash_query: BlockhashQuery::Validated(
4320 Source::NonceAccount(nonce_account),
4321 nonce_hash
4322 ),
4323 nonce_account: Some(nonce_account),
4324 nonce_authority: 0,
4325 memo: None,
4326 fee_payer: 0,
4327 from: 0,
4328 compute_unit_price: None,
4329 },
4330 signers: vec![
4331 Box::new(Presigner::new(&offline_pubkey, &offline_sig)),
4332 Box::new(read_keypair_file(&keypair_file).unwrap())
4333 ],
4334 }
4335 );
4336
4337 let vote_account_pubkey = solana_pubkey::new_rand();
4339 let vote_account_string = vote_account_pubkey.to_string();
4340 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4341 "test",
4342 "delegate-stake",
4343 &stake_account_string,
4344 &vote_account_string,
4345 ]);
4346 assert_eq!(
4347 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4348 CliCommandInfo {
4349 command: CliCommand::DelegateStake {
4350 stake_account_pubkey,
4351 vote_account_pubkey,
4352 stake_authority: 0,
4353 force: false,
4354 sign_only: false,
4355 dump_transaction_message: false,
4356 blockhash_query: BlockhashQuery::default(),
4357 nonce_account: None,
4358 nonce_authority: 0,
4359 memo: None,
4360 fee_payer: 0,
4361 compute_unit_price: None,
4362 },
4363 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4364 }
4365 );
4366
4367 let vote_account_pubkey = solana_pubkey::new_rand();
4369 let vote_account_string = vote_account_pubkey.to_string();
4370 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4371 "test",
4372 "delegate-stake",
4373 &stake_account_string,
4374 &vote_account_string,
4375 "--stake-authority",
4376 &stake_authority_keypair_file,
4377 ]);
4378 assert_eq!(
4379 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4380 CliCommandInfo {
4381 command: CliCommand::DelegateStake {
4382 stake_account_pubkey,
4383 vote_account_pubkey,
4384 stake_authority: 1,
4385 force: false,
4386 sign_only: false,
4387 dump_transaction_message: false,
4388 blockhash_query: BlockhashQuery::default(),
4389 nonce_account: None,
4390 nonce_authority: 0,
4391 memo: None,
4392 fee_payer: 0,
4393 compute_unit_price: None,
4394 },
4395 signers: vec![
4396 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4397 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4398 ],
4399 }
4400 );
4401
4402 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4404 "test",
4405 "delegate-stake",
4406 "--force",
4407 &stake_account_string,
4408 &vote_account_string,
4409 ]);
4410 assert_eq!(
4411 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4412 CliCommandInfo {
4413 command: CliCommand::DelegateStake {
4414 stake_account_pubkey,
4415 vote_account_pubkey,
4416 stake_authority: 0,
4417 force: true,
4418 sign_only: false,
4419 dump_transaction_message: false,
4420 blockhash_query: BlockhashQuery::default(),
4421 nonce_account: None,
4422 nonce_authority: 0,
4423 memo: None,
4424 fee_payer: 0,
4425 compute_unit_price: None,
4426 },
4427 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4428 }
4429 );
4430
4431 let blockhash = Hash::default();
4433 let blockhash_string = format!("{blockhash}");
4434 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4435 "test",
4436 "delegate-stake",
4437 &stake_account_string,
4438 &vote_account_string,
4439 "--blockhash",
4440 &blockhash_string,
4441 ]);
4442 assert_eq!(
4443 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4444 CliCommandInfo {
4445 command: CliCommand::DelegateStake {
4446 stake_account_pubkey,
4447 vote_account_pubkey,
4448 stake_authority: 0,
4449 force: false,
4450 sign_only: false,
4451 dump_transaction_message: false,
4452 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4453 nonce_account: None,
4454 nonce_authority: 0,
4455 memo: None,
4456 fee_payer: 0,
4457 compute_unit_price: None,
4458 },
4459 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4460 }
4461 );
4462
4463 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4464 "test",
4465 "delegate-stake",
4466 &stake_account_string,
4467 &vote_account_string,
4468 "--blockhash",
4469 &blockhash_string,
4470 "--sign-only",
4471 ]);
4472 assert_eq!(
4473 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4474 CliCommandInfo {
4475 command: CliCommand::DelegateStake {
4476 stake_account_pubkey,
4477 vote_account_pubkey,
4478 stake_authority: 0,
4479 force: false,
4480 sign_only: true,
4481 dump_transaction_message: false,
4482 blockhash_query: BlockhashQuery::Static(blockhash),
4483 nonce_account: None,
4484 nonce_authority: 0,
4485 memo: None,
4486 fee_payer: 0,
4487 compute_unit_price: None,
4488 },
4489 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4490 }
4491 );
4492
4493 let key1 = solana_pubkey::new_rand();
4495 let sig1 = Keypair::new().sign_message(&[0u8]);
4496 let signer1 = format!("{key1}={sig1}");
4497 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4498 "test",
4499 "delegate-stake",
4500 &stake_account_string,
4501 &vote_account_string,
4502 "--blockhash",
4503 &blockhash_string,
4504 "--signer",
4505 &signer1,
4506 "--fee-payer",
4507 &key1.to_string(),
4508 ]);
4509 assert_eq!(
4510 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4511 CliCommandInfo {
4512 command: CliCommand::DelegateStake {
4513 stake_account_pubkey,
4514 vote_account_pubkey,
4515 stake_authority: 0,
4516 force: false,
4517 sign_only: false,
4518 dump_transaction_message: false,
4519 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4520 nonce_account: None,
4521 nonce_authority: 0,
4522 memo: None,
4523 fee_payer: 1,
4524 compute_unit_price: None,
4525 },
4526 signers: vec![
4527 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4528 Box::new(Presigner::new(&key1, &sig1))
4529 ],
4530 }
4531 );
4532
4533 let key2 = solana_pubkey::new_rand();
4535 let sig2 = Keypair::new().sign_message(&[0u8]);
4536 let signer2 = format!("{key2}={sig2}");
4537 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4538 "test",
4539 "delegate-stake",
4540 &stake_account_string,
4541 &vote_account_string,
4542 "--blockhash",
4543 &blockhash_string,
4544 "--signer",
4545 &signer1,
4546 "--signer",
4547 &signer2,
4548 "--fee-payer",
4549 &key1.to_string(),
4550 "--nonce",
4551 &nonce_account.to_string(),
4552 "--nonce-authority",
4553 &key2.to_string(),
4554 ]);
4555 assert_eq!(
4556 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4557 CliCommandInfo {
4558 command: CliCommand::DelegateStake {
4559 stake_account_pubkey,
4560 vote_account_pubkey,
4561 stake_authority: 0,
4562 force: false,
4563 sign_only: false,
4564 dump_transaction_message: false,
4565 blockhash_query: BlockhashQuery::Validated(
4566 Source::NonceAccount(nonce_account),
4567 blockhash
4568 ),
4569 nonce_account: Some(nonce_account),
4570 nonce_authority: 2,
4571 memo: None,
4572 fee_payer: 1,
4573 compute_unit_price: None,
4574 },
4575 signers: vec![
4576 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4577 Box::new(Presigner::new(&key1, &sig1)),
4578 Box::new(Presigner::new(&key2, &sig2)),
4579 ],
4580 }
4581 );
4582
4583 let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4585 let fee_payer_keypair = Keypair::new();
4586 write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4587 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4588 "test",
4589 "delegate-stake",
4590 &stake_account_string,
4591 &vote_account_string,
4592 "--fee-payer",
4593 &fee_payer_keypair_file,
4594 ]);
4595 assert_eq!(
4596 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4597 CliCommandInfo {
4598 command: CliCommand::DelegateStake {
4599 stake_account_pubkey,
4600 vote_account_pubkey,
4601 stake_authority: 0,
4602 force: false,
4603 sign_only: false,
4604 dump_transaction_message: false,
4605 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4606 nonce_account: None,
4607 nonce_authority: 0,
4608 memo: None,
4609 fee_payer: 1,
4610 compute_unit_price: None,
4611 },
4612 signers: vec![
4613 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4614 Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
4615 ],
4616 }
4617 );
4618
4619 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4621 "test",
4622 "withdraw-stake",
4623 &stake_account_string,
4624 &stake_account_string,
4625 "42",
4626 ]);
4627
4628 assert_eq!(
4629 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4630 CliCommandInfo {
4631 command: CliCommand::WithdrawStake {
4632 stake_account_pubkey,
4633 destination_account_pubkey: stake_account_pubkey,
4634 amount: SpendAmount::Some(42_000_000_000),
4635 withdraw_authority: 0,
4636 custodian: None,
4637 sign_only: false,
4638 dump_transaction_message: false,
4639 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4640 nonce_account: None,
4641 nonce_authority: 0,
4642 memo: None,
4643 seed: None,
4644 fee_payer: 0,
4645 compute_unit_price: None,
4646 },
4647 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4648 }
4649 );
4650
4651 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4653 "test",
4654 "withdraw-stake",
4655 &stake_account_string,
4656 &stake_account_string,
4657 "AVAILABLE",
4658 ]);
4659
4660 assert_eq!(
4661 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4662 CliCommandInfo {
4663 command: CliCommand::WithdrawStake {
4664 stake_account_pubkey,
4665 destination_account_pubkey: stake_account_pubkey,
4666 amount: SpendAmount::Available,
4667 withdraw_authority: 0,
4668 custodian: None,
4669 sign_only: false,
4670 dump_transaction_message: false,
4671 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4672 nonce_account: None,
4673 nonce_authority: 0,
4674 memo: None,
4675 seed: None,
4676 fee_payer: 0,
4677 compute_unit_price: None,
4678 },
4679 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4680 }
4681 );
4682
4683 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4685 "test",
4686 "withdraw-stake",
4687 &stake_account_string,
4688 &stake_account_string,
4689 "42",
4690 "--with-compute-unit-price",
4691 "99",
4692 ]);
4693
4694 assert_eq!(
4695 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4696 CliCommandInfo {
4697 command: CliCommand::WithdrawStake {
4698 stake_account_pubkey,
4699 destination_account_pubkey: stake_account_pubkey,
4700 amount: SpendAmount::Some(42_000_000_000),
4701 withdraw_authority: 0,
4702 custodian: None,
4703 sign_only: false,
4704 dump_transaction_message: false,
4705 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4706 nonce_account: None,
4707 nonce_authority: 0,
4708 memo: None,
4709 seed: None,
4710 fee_payer: 0,
4711 compute_unit_price: Some(99),
4712 },
4713 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4714 }
4715 );
4716
4717 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4719 "test",
4720 "withdraw-stake",
4721 &stake_account_string,
4722 &stake_account_string,
4723 "42",
4724 "--withdraw-authority",
4725 &stake_authority_keypair_file,
4726 ]);
4727
4728 assert_eq!(
4729 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4730 CliCommandInfo {
4731 command: CliCommand::WithdrawStake {
4732 stake_account_pubkey,
4733 destination_account_pubkey: stake_account_pubkey,
4734 amount: SpendAmount::Some(42_000_000_000),
4735 withdraw_authority: 1,
4736 custodian: None,
4737 sign_only: false,
4738 dump_transaction_message: false,
4739 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4740 nonce_account: None,
4741 nonce_authority: 0,
4742 memo: None,
4743 seed: None,
4744 fee_payer: 0,
4745 compute_unit_price: None,
4746 },
4747 signers: vec![
4748 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4749 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4750 ],
4751 }
4752 );
4753
4754 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4756 "test",
4757 "withdraw-stake",
4758 &stake_account_string,
4759 &stake_account_string,
4760 "42",
4761 "--custodian",
4762 &custodian_keypair_file,
4763 ]);
4764
4765 assert_eq!(
4766 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4767 CliCommandInfo {
4768 command: CliCommand::WithdrawStake {
4769 stake_account_pubkey,
4770 destination_account_pubkey: stake_account_pubkey,
4771 amount: SpendAmount::Some(42_000_000_000),
4772 withdraw_authority: 0,
4773 custodian: Some(1),
4774 sign_only: false,
4775 dump_transaction_message: false,
4776 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4777 nonce_account: None,
4778 nonce_authority: 0,
4779 memo: None,
4780 seed: None,
4781 fee_payer: 0,
4782 compute_unit_price: None,
4783 },
4784 signers: vec![
4785 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4786 Box::new(read_keypair_file(&custodian_keypair_file).unwrap())
4787 ],
4788 }
4789 );
4790
4791 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4793 "test",
4794 "withdraw-stake",
4795 &stake_account_string,
4796 &stake_account_string,
4797 "42",
4798 "--withdraw-authority",
4799 &stake_authority_keypair_file,
4800 "--blockhash",
4801 &nonce_hash_string,
4802 "--nonce",
4803 &nonce_account_string,
4804 "--nonce-authority",
4805 &offline_string,
4806 "--fee-payer",
4807 &offline_string,
4808 "--signer",
4809 &offline_signer,
4810 ]);
4811
4812 assert_eq!(
4813 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4814 CliCommandInfo {
4815 command: CliCommand::WithdrawStake {
4816 stake_account_pubkey,
4817 destination_account_pubkey: stake_account_pubkey,
4818 amount: SpendAmount::Some(42_000_000_000),
4819 withdraw_authority: 0,
4820 custodian: None,
4821 sign_only: false,
4822 dump_transaction_message: false,
4823 blockhash_query: BlockhashQuery::Validated(
4824 Source::NonceAccount(nonce_account),
4825 nonce_hash
4826 ),
4827 nonce_account: Some(nonce_account),
4828 nonce_authority: 1,
4829 memo: None,
4830 seed: None,
4831 fee_payer: 1,
4832 compute_unit_price: None,
4833 },
4834 signers: vec![
4835 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
4836 Box::new(Presigner::new(&offline_pubkey, &offline_sig))
4837 ],
4838 }
4839 );
4840
4841 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4843 "test",
4844 "deactivate-stake",
4845 &stake_account_string,
4846 ]);
4847 assert_eq!(
4848 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4849 CliCommandInfo {
4850 command: CliCommand::DeactivateStake {
4851 stake_account_pubkey,
4852 stake_authority: 0,
4853 sign_only: false,
4854 deactivate_delinquent: false,
4855 dump_transaction_message: false,
4856 blockhash_query: BlockhashQuery::default(),
4857 nonce_account: None,
4858 nonce_authority: 0,
4859 memo: None,
4860 seed: None,
4861 fee_payer: 0,
4862 compute_unit_price: None,
4863 },
4864 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4865 }
4866 );
4867
4868 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4870 "test",
4871 "deactivate-stake",
4872 &stake_account_string,
4873 "--delinquent",
4874 ]);
4875 assert_eq!(
4876 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4877 CliCommandInfo {
4878 command: CliCommand::DeactivateStake {
4879 stake_account_pubkey,
4880 stake_authority: 0,
4881 sign_only: false,
4882 deactivate_delinquent: true,
4883 dump_transaction_message: false,
4884 blockhash_query: BlockhashQuery::default(),
4885 nonce_account: None,
4886 nonce_authority: 0,
4887 memo: None,
4888 seed: None,
4889 fee_payer: 0,
4890 compute_unit_price: None,
4891 },
4892 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4893 }
4894 );
4895
4896 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4898 "test",
4899 "deactivate-stake",
4900 &stake_account_string,
4901 "--stake-authority",
4902 &stake_authority_keypair_file,
4903 ]);
4904 assert_eq!(
4905 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4906 CliCommandInfo {
4907 command: CliCommand::DeactivateStake {
4908 stake_account_pubkey,
4909 stake_authority: 1,
4910 sign_only: false,
4911 deactivate_delinquent: false,
4912 dump_transaction_message: false,
4913 blockhash_query: BlockhashQuery::default(),
4914 nonce_account: None,
4915 nonce_authority: 0,
4916 memo: None,
4917 seed: None,
4918 fee_payer: 0,
4919 compute_unit_price: None,
4920 },
4921 signers: vec![
4922 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4923 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4924 ],
4925 }
4926 );
4927
4928 let blockhash = Hash::default();
4930 let blockhash_string = format!("{blockhash}");
4931 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4932 "test",
4933 "deactivate-stake",
4934 &stake_account_string,
4935 "--blockhash",
4936 &blockhash_string,
4937 ]);
4938 assert_eq!(
4939 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4940 CliCommandInfo {
4941 command: CliCommand::DeactivateStake {
4942 stake_account_pubkey,
4943 stake_authority: 0,
4944 sign_only: false,
4945 deactivate_delinquent: false,
4946 dump_transaction_message: false,
4947 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4948 nonce_account: None,
4949 nonce_authority: 0,
4950 memo: None,
4951 seed: None,
4952 fee_payer: 0,
4953 compute_unit_price: None,
4954 },
4955 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4956 }
4957 );
4958
4959 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4960 "test",
4961 "deactivate-stake",
4962 &stake_account_string,
4963 "--blockhash",
4964 &blockhash_string,
4965 "--sign-only",
4966 ]);
4967 assert_eq!(
4968 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4969 CliCommandInfo {
4970 command: CliCommand::DeactivateStake {
4971 stake_account_pubkey,
4972 stake_authority: 0,
4973 sign_only: true,
4974 deactivate_delinquent: false,
4975 dump_transaction_message: false,
4976 blockhash_query: BlockhashQuery::Static(blockhash),
4977 nonce_account: None,
4978 nonce_authority: 0,
4979 memo: None,
4980 seed: None,
4981 fee_payer: 0,
4982 compute_unit_price: None,
4983 },
4984 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4985 }
4986 );
4987
4988 let key1 = solana_pubkey::new_rand();
4990 let sig1 = Keypair::new().sign_message(&[0u8]);
4991 let signer1 = format!("{key1}={sig1}");
4992 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4993 "test",
4994 "deactivate-stake",
4995 &stake_account_string,
4996 "--blockhash",
4997 &blockhash_string,
4998 "--signer",
4999 &signer1,
5000 "--fee-payer",
5001 &key1.to_string(),
5002 ]);
5003 assert_eq!(
5004 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5005 CliCommandInfo {
5006 command: CliCommand::DeactivateStake {
5007 stake_account_pubkey,
5008 stake_authority: 0,
5009 sign_only: false,
5010 deactivate_delinquent: false,
5011 dump_transaction_message: false,
5012 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
5013 nonce_account: None,
5014 nonce_authority: 0,
5015 memo: None,
5016 seed: None,
5017 fee_payer: 1,
5018 compute_unit_price: None,
5019 },
5020 signers: vec![
5021 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5022 Box::new(Presigner::new(&key1, &sig1))
5023 ],
5024 }
5025 );
5026
5027 let key2 = solana_pubkey::new_rand();
5029 let sig2 = Keypair::new().sign_message(&[0u8]);
5030 let signer2 = format!("{key2}={sig2}");
5031 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5032 "test",
5033 "deactivate-stake",
5034 &stake_account_string,
5035 "--blockhash",
5036 &blockhash_string,
5037 "--signer",
5038 &signer1,
5039 "--signer",
5040 &signer2,
5041 "--fee-payer",
5042 &key1.to_string(),
5043 "--nonce",
5044 &nonce_account.to_string(),
5045 "--nonce-authority",
5046 &key2.to_string(),
5047 ]);
5048 assert_eq!(
5049 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5050 CliCommandInfo {
5051 command: CliCommand::DeactivateStake {
5052 stake_account_pubkey,
5053 stake_authority: 0,
5054 sign_only: false,
5055 deactivate_delinquent: false,
5056 dump_transaction_message: false,
5057 blockhash_query: BlockhashQuery::Validated(
5058 Source::NonceAccount(nonce_account),
5059 blockhash
5060 ),
5061 nonce_account: Some(nonce_account),
5062 nonce_authority: 2,
5063 memo: None,
5064 seed: None,
5065 fee_payer: 1,
5066 compute_unit_price: None,
5067 },
5068 signers: vec![
5069 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5070 Box::new(Presigner::new(&key1, &sig1)),
5071 Box::new(Presigner::new(&key2, &sig2)),
5072 ],
5073 }
5074 );
5075
5076 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5078 "test",
5079 "deactivate-stake",
5080 &stake_account_string,
5081 "--fee-payer",
5082 &fee_payer_keypair_file,
5083 ]);
5084 assert_eq!(
5085 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5086 CliCommandInfo {
5087 command: CliCommand::DeactivateStake {
5088 stake_account_pubkey,
5089 stake_authority: 0,
5090 sign_only: false,
5091 deactivate_delinquent: false,
5092 dump_transaction_message: false,
5093 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
5094 nonce_account: None,
5095 nonce_authority: 0,
5096 memo: None,
5097 seed: None,
5098 fee_payer: 1,
5099 compute_unit_price: None,
5100 },
5101 signers: vec![
5102 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5103 Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
5104 ],
5105 }
5106 );
5107
5108 let (keypair_file, mut tmp_file) = make_tmp_file();
5110 let stake_account_keypair = Keypair::new();
5111 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5112 let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
5113 let split_stake_account_keypair = Keypair::new();
5114 write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5115
5116 let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5117 "test",
5118 "split-stake",
5119 &keypair_file,
5120 &split_stake_account_keypair_file,
5121 "50",
5122 ]);
5123 assert_eq!(
5124 parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5125 CliCommandInfo {
5126 command: CliCommand::SplitStake {
5127 stake_account_pubkey: stake_account_keypair.pubkey(),
5128 stake_authority: 0,
5129 sign_only: false,
5130 dump_transaction_message: false,
5131 blockhash_query: BlockhashQuery::default(),
5132 nonce_account: None,
5133 nonce_authority: 0,
5134 memo: None,
5135 split_stake_account: 1,
5136 seed: None,
5137 lamports: 50_000_000_000,
5138 fee_payer: 0,
5139 compute_unit_price: None,
5140 rent_exempt_reserve: None,
5141 },
5142 signers: vec![
5143 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5144 Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap())
5145 ],
5146 }
5147 );
5148
5149 let nonce_account = Pubkey::from([1u8; 32]);
5151 let nonce_account_string = nonce_account.to_string();
5152 let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
5153 let nonce_auth_pubkey = nonce_auth.pubkey();
5154 let nonce_auth_string = nonce_auth_pubkey.to_string();
5155 let nonce_sig = nonce_auth.sign_message(&[0u8]);
5156 let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
5157 let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
5158 let stake_auth_pubkey = stake_auth.pubkey();
5159 let stake_auth_string = stake_auth_pubkey.to_string();
5160 let stake_sig = stake_auth.sign_message(&[0u8]);
5161 let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
5162 let nonce_hash = Hash::new_from_array([4u8; 32]);
5163 let nonce_hash_string = nonce_hash.to_string();
5164
5165 let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5166 "test",
5167 "split-stake",
5168 &keypair_file,
5169 &split_stake_account_keypair_file,
5170 "50",
5171 "--stake-authority",
5172 &stake_auth_string,
5173 "--blockhash",
5174 &nonce_hash_string,
5175 "--nonce",
5176 &nonce_account_string,
5177 "--nonce-authority",
5178 &nonce_auth_string,
5179 "--fee-payer",
5180 &nonce_auth_string, "--signer",
5182 &nonce_signer,
5183 "--signer",
5184 &stake_signer,
5185 ]);
5186 assert_eq!(
5187 parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5188 CliCommandInfo {
5189 command: CliCommand::SplitStake {
5190 stake_account_pubkey: stake_account_keypair.pubkey(),
5191 stake_authority: 0,
5192 sign_only: false,
5193 dump_transaction_message: false,
5194 blockhash_query: BlockhashQuery::Validated(
5195 Source::NonceAccount(nonce_account),
5196 nonce_hash
5197 ),
5198 nonce_account: Some(nonce_account),
5199 nonce_authority: 1,
5200 memo: None,
5201 split_stake_account: 2,
5202 seed: None,
5203 lamports: 50_000_000_000,
5204 fee_payer: 1,
5205 compute_unit_price: None,
5206 rent_exempt_reserve: None,
5207 },
5208 signers: vec![
5209 Box::new(Presigner::new(&stake_auth_pubkey, &stake_sig)),
5210 Box::new(Presigner::new(&nonce_auth_pubkey, &nonce_sig)),
5211 Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap()),
5212 ],
5213 }
5214 );
5215
5216 let (keypair_file, mut tmp_file) = make_tmp_file();
5218 let stake_account_keypair = Keypair::new();
5219 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5220
5221 let source_stake_account_pubkey = solana_pubkey::new_rand();
5222 let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
5223 "test",
5224 "merge-stake",
5225 &keypair_file,
5226 &source_stake_account_pubkey.to_string(),
5227 ]);
5228 assert_eq!(
5229 parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
5230 CliCommandInfo {
5231 command: CliCommand::MergeStake {
5232 stake_account_pubkey: stake_account_keypair.pubkey(),
5233 source_stake_account_pubkey,
5234 stake_authority: 0,
5235 sign_only: false,
5236 dump_transaction_message: false,
5237 blockhash_query: BlockhashQuery::default(),
5238 nonce_account: None,
5239 nonce_authority: 0,
5240 memo: None,
5241 fee_payer: 0,
5242 compute_unit_price: None,
5243 },
5244 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
5245 }
5246 );
5247 }
5248}