1use {
2 crate::{
3 checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4 cli::{
5 CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6 log_instruction_custom_error,
7 },
8 compute_budget::{
9 ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
10 },
11 feature::get_feature_activation_epoch,
12 memo::WithMemo,
13 nonce::check_nonce_account,
14 spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balances},
15 },
16 clap::{App, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand, value_t},
17 solana_account::Account,
18 solana_clap_utils::{
19 ArgConstant,
20 compute_budget::{COMPUTE_UNIT_PRICE_ARG, ComputeUnitLimit, compute_unit_price_arg},
21 fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
22 hidden_unless_forced,
23 input_parsers::*,
24 input_validators::*,
25 keypair::{DefaultSigner, SignerIndex},
26 memo::{MEMO_ARG, memo_arg},
27 nonce::*,
28 offline::*,
29 },
30 solana_cli_output::{
31 self, CliBalance, CliEpochReward, CliStakeHistory, CliStakeHistoryEntry, CliStakeState,
32 CliStakeType, OutputFormat, ReturnSignersConfig, display::BuildBalanceMessageConfig,
33 return_signers_with_config, stdout::writeln_stdout,
34 },
35 solana_clock::{Clock, Epoch, SECONDS_PER_DAY, UnixTimestamp},
36 solana_commitment_config::CommitmentConfig,
37 solana_epoch_schedule::EpochSchedule,
38 solana_message::Message,
39 solana_native_token::Sol,
40 solana_pubkey::Pubkey,
41 solana_remote_wallet::remote_wallet::RemoteWalletManager,
42 solana_rpc_client::nonblocking::rpc_client::RpcClient,
43 solana_rpc_client_api::{
44 config::RpcGetVoteAccountsConfig,
45 request::DELINQUENT_VALIDATOR_SLOT_DISTANCE,
46 response::{RpcInflationReward, RpcVoteAccountStatus},
47 },
48 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
49 solana_sdk_ids::{
50 system_program,
51 sysvar::{clock, stake_history},
52 },
53 solana_stake_history::StakeHistory,
54 solana_stake_interface::{
55 self as stake,
56 error::StakeError,
57 instruction::{self as stake_instruction, LockupArgs},
58 state::{
59 Authorized, Delegation, Lockup, Meta, StakeActivationStatus, StakeAuthorize,
60 StakeStateV2,
61 },
62 tools::{acceptable_reference_epoch_credits, eligible_for_deactivate_delinquent},
63 },
64 solana_system_interface::{error::SystemError, instruction as system_instruction},
65 solana_transaction::Transaction,
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 wincode::deserialize::<StakeStateV2>(&stake_account.data) {
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) -> StakeActivationStatus {
2446 delegation.stake_activating_and_deactivating_v2(
2447 current_epoch,
2448 stake_history,
2449 new_rate_activation_epoch,
2450 )
2451}
2452
2453pub fn build_stake_state(
2454 account_balance: u64,
2455 stake_state: &StakeStateV2,
2456 use_lamports_unit: bool,
2457 stake_history: &StakeHistory,
2458 clock: &Clock,
2459 new_rate_activation_epoch: Option<Epoch>,
2460 rent_exempt_reserve: u64,
2461 use_csv: bool,
2462) -> CliStakeState {
2463 match stake_state {
2464 StakeStateV2::Stake(
2465 Meta {
2466 #[expect(deprecated)]
2467 rent_exempt_reserve: _,
2468 authorized,
2469 lockup,
2470 },
2471 stake,
2472 _,
2473 ) => {
2474 let current_epoch = clock.epoch;
2475 let StakeActivationStatus {
2476 effective,
2477 activating,
2478 deactivating,
2479 } = stake_activation_status(
2480 &stake.delegation,
2481 current_epoch,
2482 stake_history,
2483 new_rate_activation_epoch,
2484 );
2485 let lockup = if lockup.is_in_force(clock, None) {
2486 Some(lockup.into())
2487 } else {
2488 None
2489 };
2490 CliStakeState {
2491 stake_type: CliStakeType::Stake,
2492 account_balance,
2493 credits_observed: Some(stake.credits_observed),
2494 delegated_stake: Some(stake.delegation.stake),
2495 delegated_vote_account_address: if stake.delegation.voter_pubkey
2496 != Pubkey::default()
2497 {
2498 Some(stake.delegation.voter_pubkey.to_string())
2499 } else {
2500 None
2501 },
2502 activation_epoch: Some(if stake.delegation.activation_epoch < u64::MAX {
2503 stake.delegation.activation_epoch
2504 } else {
2505 0
2506 }),
2507 deactivation_epoch: if stake.delegation.deactivation_epoch < u64::MAX {
2508 Some(stake.delegation.deactivation_epoch)
2509 } else {
2510 None
2511 },
2512 authorized: Some(authorized.into()),
2513 lockup,
2514 use_lamports_unit,
2515 current_epoch,
2516 rent_exempt_reserve: Some(rent_exempt_reserve),
2517 active_stake: u64_some_if_not_zero(effective),
2518 activating_stake: u64_some_if_not_zero(activating),
2519 deactivating_stake: u64_some_if_not_zero(deactivating),
2520 use_csv,
2521 ..CliStakeState::default()
2522 }
2523 }
2524 StakeStateV2::RewardsPool => CliStakeState {
2525 stake_type: CliStakeType::RewardsPool,
2526 account_balance,
2527 ..CliStakeState::default()
2528 },
2529 StakeStateV2::Uninitialized => CliStakeState {
2530 account_balance,
2531 ..CliStakeState::default()
2532 },
2533 StakeStateV2::Initialized(Meta {
2534 #[expect(deprecated)]
2535 rent_exempt_reserve: _,
2536 authorized,
2537 lockup,
2538 }) => {
2539 let lockup = if lockup.is_in_force(clock, None) {
2540 Some(lockup.into())
2541 } else {
2542 None
2543 };
2544 CliStakeState {
2545 stake_type: CliStakeType::Initialized,
2546 account_balance,
2547 credits_observed: Some(0),
2548 authorized: Some(authorized.into()),
2549 lockup,
2550 use_lamports_unit,
2551 rent_exempt_reserve: Some(rent_exempt_reserve),
2552 ..CliStakeState::default()
2553 }
2554 }
2555 }
2556}
2557
2558async fn get_stake_account_state(
2559 rpc_client: &RpcClient,
2560 stake_account_pubkey: &Pubkey,
2561 commitment_config: CommitmentConfig,
2562) -> Result<StakeStateV2, Box<dyn std::error::Error>> {
2563 let stake_account = rpc_client
2564 .get_account_with_commitment(stake_account_pubkey, commitment_config)
2565 .await?
2566 .value
2567 .ok_or_else(|| {
2568 CliError::RpcRequestError(format!("{stake_account_pubkey:?} account does not exist"))
2569 })?;
2570 if stake_account.owner != stake::program::id() {
2571 return Err(CliError::RpcRequestError(format!(
2572 "{stake_account_pubkey:?} is not a stake account",
2573 ))
2574 .into());
2575 }
2576 wincode::deserialize(&stake_account.data).map_err(|err| {
2577 CliError::RpcRequestError(format!(
2578 "Account data could not be deserialized to stake state: {err}"
2579 ))
2580 .into()
2581 })
2582}
2583
2584pub(crate) fn check_current_authority(
2585 permitted_authorities: &[Pubkey],
2586 provided_current_authority: &Pubkey,
2587) -> Result<(), CliError> {
2588 if !permitted_authorities.contains(provided_current_authority) {
2589 Err(CliError::RpcRequestError(format!(
2590 "Invalid authority provided: {provided_current_authority:?}, expected \
2591 {permitted_authorities:?}"
2592 )))
2593 } else {
2594 Ok(())
2595 }
2596}
2597
2598pub async fn get_epoch_boundary_timestamps(
2599 rpc_client: &RpcClient,
2600 reward: &RpcInflationReward,
2601 epoch_schedule: &EpochSchedule,
2602) -> Result<(UnixTimestamp, UnixTimestamp), Box<dyn std::error::Error>> {
2603 let epoch_end_time = rpc_client.get_block_time(reward.effective_slot).await?;
2604 let mut epoch_start_slot = epoch_schedule.get_first_slot_in_epoch(reward.epoch);
2605 let epoch_start_time = loop {
2606 if epoch_start_slot >= reward.effective_slot {
2607 return Err("epoch_start_time not found".to_string().into());
2608 }
2609 match rpc_client.get_block_time(epoch_start_slot).await {
2610 Ok(block_time) => {
2611 break block_time;
2612 }
2613 Err(_) => {
2614 epoch_start_slot = epoch_start_slot
2618 .checked_add(1)
2619 .ok_or("Reached last slot that fits into u64")?;
2620 }
2621 }
2622 };
2623 Ok((epoch_start_time, epoch_end_time))
2624}
2625
2626pub fn make_cli_reward(
2627 reward: &RpcInflationReward,
2628 block_time: UnixTimestamp,
2629 epoch_start_time: UnixTimestamp,
2630 epoch_end_time: UnixTimestamp,
2631) -> Option<CliEpochReward> {
2632 let wallclock_epoch_duration = epoch_end_time.checked_sub(epoch_start_time)?;
2633 if reward.post_balance > reward.amount {
2634 let rate_change =
2635 reward.amount as f64 / (reward.post_balance.saturating_sub(reward.amount)) as f64;
2636
2637 let wallclock_epochs_per_year =
2638 (SECONDS_PER_DAY * 365) as f64 / wallclock_epoch_duration as f64;
2639 let apr = rate_change * wallclock_epochs_per_year;
2640
2641 Some(CliEpochReward {
2642 epoch: reward.epoch,
2643 effective_slot: reward.effective_slot,
2644 amount: reward.amount,
2645 post_balance: reward.post_balance,
2646 percent_change: rate_change * 100.0,
2647 apr: Some(apr * 100.0),
2648 commission: reward.commission,
2649 block_time,
2650 })
2651 } else {
2652 None
2653 }
2654}
2655
2656pub(crate) async fn fetch_epoch_rewards(
2657 rpc_client: &RpcClient,
2658 address: &Pubkey,
2659 mut num_epochs: usize,
2660 starting_epoch: Option<u64>,
2661) -> Result<Vec<CliEpochReward>, Box<dyn std::error::Error>> {
2662 let mut all_epoch_rewards = vec![];
2663 let epoch_schedule = rpc_client.get_epoch_schedule().await?;
2664 let mut rewards_epoch = if let Some(epoch) = starting_epoch {
2665 epoch
2666 } else {
2667 rpc_client
2668 .get_epoch_info()
2669 .await?
2670 .epoch
2671 .saturating_sub(num_epochs as u64)
2672 };
2673
2674 while num_epochs > 0 {
2675 if let Ok(rewards) = rpc_client
2676 .get_inflation_reward(&[*address], Some(rewards_epoch))
2677 .await
2678 {
2679 if let Some(reward) = &rewards[0] {
2680 let (epoch_start_time, epoch_end_time) =
2681 get_epoch_boundary_timestamps(rpc_client, reward, &epoch_schedule).await?;
2682 let block_time = rpc_client.get_block_time(reward.effective_slot).await?;
2683 if let Some(cli_reward) =
2684 make_cli_reward(reward, block_time, epoch_start_time, epoch_end_time)
2685 {
2686 all_epoch_rewards.push(cli_reward);
2687 }
2688 }
2689 } else {
2690 eprintln!("Rewards not available for epoch {rewards_epoch}");
2691 }
2692 num_epochs = num_epochs.saturating_sub(1);
2693 rewards_epoch = rewards_epoch.saturating_add(1);
2694 }
2695
2696 Ok(all_epoch_rewards)
2697}
2698
2699pub async fn process_show_stake_account(
2700 rpc_client: &RpcClient,
2701 config: &CliConfig<'_>,
2702 stake_account_address: &Pubkey,
2703 use_lamports_unit: bool,
2704 with_rewards: Option<usize>,
2705 use_csv: bool,
2706 starting_epoch: Option<u64>,
2707) -> ProcessResult {
2708 let stake_account = rpc_client.get_account(stake_account_address).await?;
2709 let state = get_account_stake_state(
2710 rpc_client,
2711 stake_account_address,
2712 stake_account,
2713 use_lamports_unit,
2714 with_rewards,
2715 use_csv,
2716 starting_epoch,
2717 )
2718 .await?;
2719 Ok(config.output_format.formatted_string(&state))
2720}
2721
2722pub async fn get_account_stake_state(
2723 rpc_client: &RpcClient,
2724 stake_account_address: &Pubkey,
2725 stake_account: solana_account::Account,
2726 use_lamports_unit: bool,
2727 with_rewards: Option<usize>,
2728 use_csv: bool,
2729 starting_epoch: Option<u64>,
2730) -> Result<CliStakeState, CliError> {
2731 if stake_account.owner != stake::program::id() {
2732 return Err(CliError::RpcRequestError(format!(
2733 "{stake_account_address:?} is not a stake account",
2734 )));
2735 }
2736 match wincode::deserialize::<StakeStateV2>(&stake_account.data) {
2737 Ok(stake_state) => {
2738 let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2739 let stake_history: StakeHistory = wincode::deserialize(&stake_history_account.data)
2740 .map_err(|_| {
2741 CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2742 })?;
2743 let clock_account = rpc_client.get_account(&clock::id()).await?;
2744 let clock: Clock = wincode::deserialize(&clock_account.data).map_err(|_| {
2745 CliError::RpcRequestError("Failed to deserialize clock sysvar".to_string())
2746 })?;
2747 let new_rate_activation_epoch = get_feature_activation_epoch(
2748 rpc_client,
2749 &agave_feature_set::reduce_stake_warmup_cooldown::id(),
2750 )
2751 .await?;
2752 let rent_exempt_balance = rpc_client
2753 .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2754 .await?;
2755 let mut state = build_stake_state(
2756 stake_account.lamports,
2757 &stake_state,
2758 use_lamports_unit,
2759 &stake_history,
2760 &clock,
2761 new_rate_activation_epoch,
2762 rent_exempt_balance,
2763 use_csv,
2764 );
2765
2766 if state.stake_type == CliStakeType::Stake
2767 && state.activation_epoch.is_some()
2768 && let Some(num_epochs) = with_rewards
2769 {
2770 state.epoch_rewards = match fetch_epoch_rewards(
2771 rpc_client,
2772 stake_account_address,
2773 num_epochs,
2774 starting_epoch,
2775 )
2776 .await
2777 {
2778 Ok(rewards) => Some(rewards),
2779 Err(error) => {
2780 eprintln!("Failed to fetch epoch rewards: {error:?}");
2781 None
2782 }
2783 };
2784 }
2785 Ok(state)
2786 }
2787 Err(err) => Err(CliError::RpcRequestError(format!(
2788 "Account data could not be deserialized to stake state: {err}"
2789 ))),
2790 }
2791}
2792
2793pub async fn process_show_stake_history(
2794 rpc_client: &RpcClient,
2795 config: &CliConfig<'_>,
2796 use_lamports_unit: bool,
2797 limit_results: usize,
2798) -> ProcessResult {
2799 let stake_history_account = rpc_client.get_account(&stake_history::id()).await?;
2800 let stake_history =
2801 wincode::deserialize::<StakeHistory>(&stake_history_account.data).map_err(|_| {
2802 CliError::RpcRequestError("Failed to deserialize stake history".to_string())
2803 })?;
2804
2805 let limit_results = match config.output_format {
2806 OutputFormat::Json | OutputFormat::JsonCompact => usize::MAX,
2807 _ => {
2808 if limit_results == 0 {
2809 usize::MAX
2810 } else {
2811 limit_results
2812 }
2813 }
2814 };
2815 let mut entries: Vec<CliStakeHistoryEntry> = vec![];
2816 for entry in stake_history.deref().iter().take(limit_results) {
2817 entries.push(entry.into());
2818 }
2819 let stake_history_output = CliStakeHistory {
2820 entries,
2821 use_lamports_unit,
2822 };
2823 Ok(config.output_format.formatted_string(&stake_history_output))
2824}
2825
2826#[allow(clippy::too_many_arguments)]
2827pub async fn process_delegate_stake(
2828 rpc_client: &RpcClient,
2829 config: &CliConfig<'_>,
2830 stake_account_pubkey: &Pubkey,
2831 vote_account_pubkey: &Pubkey,
2832 stake_authority: SignerIndex,
2833 force: bool,
2834 sign_only: bool,
2835 dump_transaction_message: bool,
2836 blockhash_query: &BlockhashQuery,
2837 nonce_account: Option<Pubkey>,
2838 nonce_authority: SignerIndex,
2839 memo: Option<&String>,
2840 fee_payer: SignerIndex,
2841 compute_unit_price: Option<u64>,
2842) -> ProcessResult {
2843 check_unique_pubkeys(
2844 (&config.signers[0].pubkey(), "cli keypair".to_string()),
2845 (stake_account_pubkey, "stake_account_pubkey".to_string()),
2846 )?;
2847 let stake_authority = config.signers[stake_authority];
2848
2849 if !sign_only {
2850 let get_vote_accounts_config = RpcGetVoteAccountsConfig {
2853 vote_pubkey: Some(vote_account_pubkey.to_string()),
2854 keep_unstaked_delinquents: Some(true),
2855 commitment: Some(rpc_client.commitment()),
2856 ..RpcGetVoteAccountsConfig::default()
2857 };
2858 let RpcVoteAccountStatus {
2859 current,
2860 delinquent,
2861 } = rpc_client
2862 .get_vote_accounts_with_config(get_vote_accounts_config)
2863 .await?;
2864 let rpc_vote_account =
2866 current
2867 .first()
2868 .or_else(|| delinquent.first())
2869 .ok_or(CliError::RpcRequestError(format!(
2870 "Vote account not found: {vote_account_pubkey}"
2871 )))?;
2872
2873 let activated_stake = rpc_vote_account.activated_stake;
2874 let root_slot = rpc_vote_account.root_slot;
2875 let min_root_slot = rpc_client
2876 .get_slot()
2877 .await
2878 .map(|slot| slot.saturating_sub(DELINQUENT_VALIDATOR_SLOT_DISTANCE))?;
2879 let sanity_check_result = if root_slot >= min_root_slot || activated_stake == 0 {
2880 Ok(())
2881 } else if root_slot == 0 {
2882 Err(CliError::BadParameter(
2883 "Unable to delegate. Vote account has no root slot".to_string(),
2884 ))
2885 } else {
2886 Err(CliError::DynamicProgramError(format!(
2887 "Unable to delegate. Vote account appears delinquent because its current root \
2888 slot, {root_slot}, is less than {min_root_slot}"
2889 )))
2890 };
2891
2892 if let Err(err) = &sanity_check_result {
2893 if !force {
2894 sanity_check_result?;
2895 } else {
2896 writeln_stdout(format_args!("--force supplied, ignoring: {err}"))?;
2897 }
2898 }
2899
2900 if !force {
2901 let stake_account = rpc_client.get_account(stake_account_pubkey).await?;
2902 if stake_account.owner != stake::program::id() {
2903 return Err(CliError::BadParameter(format!(
2904 "{stake_account_pubkey} is not a stake account",
2905 ))
2906 .into());
2907 }
2908
2909 let rent_exempt_balance = rpc_client
2910 .get_minimum_balance_for_rent_exemption(stake_account.data.len())
2911 .await?;
2912 let minimum_delegation = rpc_client.get_stake_minimum_delegation().await?;
2913 let minimum_total_lamports = rent_exempt_balance.saturating_add(minimum_delegation);
2914 let stake_account_lamports = stake_account.lamports;
2915
2916 if stake_account_lamports < minimum_total_lamports {
2917 return Err(CliError::BadParameter(format!(
2918 "need at least {minimum_total_lamports} lamports to delegate this stake \
2919 account, available lamports: {stake_account_lamports}"
2920 ))
2921 .into());
2922 }
2923 }
2924 }
2925
2926 let recent_blockhash = blockhash_query
2927 .get_blockhash(rpc_client, config.commitment)
2928 .await?;
2929
2930 let compute_unit_limit = match blockhash_query {
2932 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
2933 BlockhashQuery::Rpc(_) => ComputeUnitLimit::SimulatedWithExtraPercentage(5),
2934 };
2935 let ixs = vec![stake_instruction::delegate_stake(
2936 stake_account_pubkey,
2937 &stake_authority.pubkey(),
2938 vote_account_pubkey,
2939 )]
2940 .with_memo(memo)
2941 .with_compute_unit_config(&ComputeUnitConfig {
2942 compute_unit_price,
2943 compute_unit_limit,
2944 });
2945
2946 let nonce_authority = config.signers[nonce_authority];
2947 let fee_payer = config.signers[fee_payer];
2948
2949 let mut message = if let Some(nonce_account) = &nonce_account {
2950 Message::new_with_nonce(
2951 ixs,
2952 Some(&fee_payer.pubkey()),
2953 nonce_account,
2954 &nonce_authority.pubkey(),
2955 )
2956 } else {
2957 Message::new(&ixs, Some(&fee_payer.pubkey()))
2958 };
2959 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
2960 let mut tx = Transaction::new_unsigned(message);
2961
2962 if sign_only {
2963 tx.try_partial_sign(&config.signers, recent_blockhash)?;
2964 return_signers_with_config(
2965 &tx,
2966 &config.output_format,
2967 &ReturnSignersConfig {
2968 dump_transaction_message,
2969 },
2970 )
2971 } else {
2972 tx.try_sign(&config.signers, recent_blockhash)?;
2973 if let Some(nonce_account) = &nonce_account {
2974 let nonce_account =
2975 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
2976 rpc_client,
2977 nonce_account,
2978 config.commitment,
2979 )
2980 .await?;
2981 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
2982 }
2983 check_account_for_fee_with_commitment(
2984 rpc_client,
2985 &tx.message.account_keys[0],
2986 &tx.message,
2987 config.commitment,
2988 )
2989 .await?;
2990 let result = rpc_client
2991 .send_and_confirm_transaction_with_spinner_and_config(
2992 &tx,
2993 config.commitment,
2994 config.send_transaction_config,
2995 )
2996 .await;
2997 log_instruction_custom_error::<StakeError>(result, config)
2998 }
2999}
3000
3001pub async fn process_stake_minimum_delegation(
3002 rpc_client: &RpcClient,
3003 config: &CliConfig<'_>,
3004 use_lamports_unit: bool,
3005) -> ProcessResult {
3006 let stake_minimum_delegation = rpc_client
3007 .get_stake_minimum_delegation_with_commitment(config.commitment)
3008 .await?;
3009
3010 let stake_minimum_delegation_output = CliBalance {
3011 lamports: stake_minimum_delegation,
3012 config: BuildBalanceMessageConfig {
3013 use_lamports_unit,
3014 show_unit: true,
3015 trim_trailing_zeros: true,
3016 },
3017 };
3018
3019 Ok(config
3020 .output_format
3021 .formatted_string(&stake_minimum_delegation_output))
3022}
3023
3024#[cfg(test)]
3025mod tests {
3026 use {
3027 super::*,
3028 crate::{clap_app::get_clap_app, cli::parse_command},
3029 solana_hash::Hash,
3030 solana_keypair::{Keypair, keypair_from_seed, read_keypair_file, write_keypair},
3031 solana_presigner::Presigner,
3032 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
3033 solana_signer::Signer,
3034 tempfile::NamedTempFile,
3035 };
3036
3037 fn make_tmp_file() -> (String, NamedTempFile) {
3038 let tmp_file = NamedTempFile::new().unwrap();
3039 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
3040 }
3041
3042 #[test]
3043 #[allow(clippy::cognitive_complexity)]
3044 fn test_parse_command() {
3045 let test_commands = get_clap_app("test", "desc", "version");
3046 let default_keypair = Keypair::new();
3047 let (default_keypair_file, mut tmp_file) = make_tmp_file();
3048 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
3049 let default_signer = DefaultSigner::new("", &default_keypair_file);
3050 let (keypair_file, mut tmp_file) = make_tmp_file();
3051 let stake_account_keypair = Keypair::new();
3052 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
3053 let stake_account_pubkey = stake_account_keypair.pubkey();
3054 let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file();
3055 let stake_authority_keypair = Keypair::new();
3056 write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap();
3057 let (custodian_keypair_file, mut tmp_file) = make_tmp_file();
3058 let custodian_keypair = Keypair::new();
3059 write_keypair(&custodian_keypair, tmp_file.as_file_mut()).unwrap();
3060
3061 let stake_account_string = stake_account_pubkey.to_string();
3063 let new_stake_authority = Pubkey::from([1u8; 32]);
3064 let new_stake_string = new_stake_authority.to_string();
3065 let new_withdraw_authority = Pubkey::from([2u8; 32]);
3066 let new_withdraw_string = new_withdraw_authority.to_string();
3067 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3068 "test",
3069 "stake-authorize",
3070 &stake_account_string,
3071 "--new-stake-authority",
3072 &new_stake_string,
3073 "--new-withdraw-authority",
3074 &new_withdraw_string,
3075 ]);
3076 assert_eq!(
3077 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3078 CliCommandInfo {
3079 command: CliCommand::StakeAuthorize {
3080 stake_account_pubkey,
3081 new_authorizations: vec![
3082 StakeAuthorizationIndexed {
3083 authorization_type: StakeAuthorize::Staker,
3084 new_authority_pubkey: new_stake_authority,
3085 authority: 0,
3086 new_authority_signer: None,
3087 },
3088 StakeAuthorizationIndexed {
3089 authorization_type: StakeAuthorize::Withdrawer,
3090 new_authority_pubkey: new_withdraw_authority,
3091 authority: 0,
3092 new_authority_signer: None,
3093 },
3094 ],
3095 sign_only: false,
3096 dump_transaction_message: false,
3097 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3098 nonce_account: None,
3099 nonce_authority: 0,
3100 memo: None,
3101 fee_payer: 0,
3102 custodian: None,
3103 no_wait: false,
3104 compute_unit_price: None,
3105 },
3106 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3107 },
3108 );
3109 let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3110 let withdraw_authority_keypair = Keypair::new();
3111 write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3112 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3113 "test",
3114 "stake-authorize",
3115 &stake_account_string,
3116 "--new-stake-authority",
3117 &new_stake_string,
3118 "--new-withdraw-authority",
3119 &new_withdraw_string,
3120 "--stake-authority",
3121 &stake_authority_keypair_file,
3122 "--withdraw-authority",
3123 &withdraw_authority_keypair_file,
3124 ]);
3125 assert_eq!(
3126 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3127 CliCommandInfo {
3128 command: CliCommand::StakeAuthorize {
3129 stake_account_pubkey,
3130 new_authorizations: vec![
3131 StakeAuthorizationIndexed {
3132 authorization_type: StakeAuthorize::Staker,
3133 new_authority_pubkey: new_stake_authority,
3134 authority: 1,
3135 new_authority_signer: None,
3136 },
3137 StakeAuthorizationIndexed {
3138 authorization_type: StakeAuthorize::Withdrawer,
3139 new_authority_pubkey: new_withdraw_authority,
3140 authority: 2,
3141 new_authority_signer: None,
3142 },
3143 ],
3144 sign_only: false,
3145 dump_transaction_message: false,
3146 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3147 nonce_account: None,
3148 nonce_authority: 0,
3149 memo: None,
3150 fee_payer: 0,
3151 custodian: None,
3152 no_wait: false,
3153 compute_unit_price: None,
3154 },
3155 signers: vec![
3156 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3157 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3158 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3159 ],
3160 },
3161 );
3162 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3164 "test",
3165 "stake-authorize",
3166 &stake_account_string,
3167 "--new-stake-authority",
3168 &new_stake_string,
3169 "--new-withdraw-authority",
3170 &new_withdraw_string,
3171 "--withdraw-authority",
3172 &withdraw_authority_keypair_file,
3173 ]);
3174 assert_eq!(
3175 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3176 CliCommandInfo {
3177 command: CliCommand::StakeAuthorize {
3178 stake_account_pubkey,
3179 new_authorizations: vec![
3180 StakeAuthorizationIndexed {
3181 authorization_type: StakeAuthorize::Staker,
3182 new_authority_pubkey: new_stake_authority,
3183 authority: 1,
3184 new_authority_signer: None,
3185 },
3186 StakeAuthorizationIndexed {
3187 authorization_type: StakeAuthorize::Withdrawer,
3188 new_authority_pubkey: new_withdraw_authority,
3189 authority: 1,
3190 new_authority_signer: None,
3191 },
3192 ],
3193 sign_only: false,
3194 dump_transaction_message: false,
3195 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3196 nonce_account: None,
3197 nonce_authority: 0,
3198 memo: None,
3199 fee_payer: 0,
3200 custodian: None,
3201 no_wait: false,
3202 compute_unit_price: None,
3203 },
3204 signers: vec![
3205 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3206 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3207 ],
3208 },
3209 );
3210 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3211 "test",
3212 "stake-authorize",
3213 &stake_account_string,
3214 "--new-stake-authority",
3215 &new_stake_string,
3216 ]);
3217 assert_eq!(
3218 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3219 CliCommandInfo {
3220 command: CliCommand::StakeAuthorize {
3221 stake_account_pubkey,
3222 new_authorizations: vec![StakeAuthorizationIndexed {
3223 authorization_type: StakeAuthorize::Staker,
3224 new_authority_pubkey: new_stake_authority,
3225 authority: 0,
3226 new_authority_signer: None,
3227 }],
3228 sign_only: false,
3229 dump_transaction_message: false,
3230 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3231 nonce_account: None,
3232 nonce_authority: 0,
3233 memo: None,
3234 fee_payer: 0,
3235 custodian: None,
3236 no_wait: false,
3237 compute_unit_price: None,
3238 },
3239 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3240 },
3241 );
3242 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3243 "test",
3244 "stake-authorize",
3245 &stake_account_string,
3246 "--new-stake-authority",
3247 &new_stake_string,
3248 "--stake-authority",
3249 &stake_authority_keypair_file,
3250 ]);
3251 assert_eq!(
3252 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3253 CliCommandInfo {
3254 command: CliCommand::StakeAuthorize {
3255 stake_account_pubkey,
3256 new_authorizations: vec![StakeAuthorizationIndexed {
3257 authorization_type: StakeAuthorize::Staker,
3258 new_authority_pubkey: new_stake_authority,
3259 authority: 1,
3260 new_authority_signer: None,
3261 }],
3262 sign_only: false,
3263 dump_transaction_message: false,
3264 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3265 nonce_account: None,
3266 nonce_authority: 0,
3267 memo: None,
3268 fee_payer: 0,
3269 custodian: None,
3270 no_wait: false,
3271 compute_unit_price: None,
3272 },
3273 signers: vec![
3274 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3275 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3276 ],
3277 },
3278 );
3279 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3281 "test",
3282 "stake-authorize",
3283 &stake_account_string,
3284 "--new-stake-authority",
3285 &new_stake_string,
3286 "--withdraw-authority",
3287 &withdraw_authority_keypair_file,
3288 ]);
3289 assert_eq!(
3290 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3291 CliCommandInfo {
3292 command: CliCommand::StakeAuthorize {
3293 stake_account_pubkey,
3294 new_authorizations: vec![StakeAuthorizationIndexed {
3295 authorization_type: StakeAuthorize::Staker,
3296 new_authority_pubkey: new_stake_authority,
3297 authority: 1,
3298 new_authority_signer: None,
3299 }],
3300 sign_only: false,
3301 dump_transaction_message: false,
3302 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3303 nonce_account: None,
3304 nonce_authority: 0,
3305 memo: None,
3306 fee_payer: 0,
3307 custodian: None,
3308 no_wait: false,
3309 compute_unit_price: None,
3310 },
3311 signers: vec![
3312 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3313 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3314 ],
3315 },
3316 );
3317 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3318 "test",
3319 "stake-authorize",
3320 &stake_account_string,
3321 "--new-withdraw-authority",
3322 &new_withdraw_string,
3323 ]);
3324 assert_eq!(
3325 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3326 CliCommandInfo {
3327 command: CliCommand::StakeAuthorize {
3328 stake_account_pubkey,
3329 new_authorizations: vec![StakeAuthorizationIndexed {
3330 authorization_type: StakeAuthorize::Withdrawer,
3331 new_authority_pubkey: new_withdraw_authority,
3332 authority: 0,
3333 new_authority_signer: None,
3334 }],
3335 sign_only: false,
3336 dump_transaction_message: false,
3337 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3338 nonce_account: None,
3339 nonce_authority: 0,
3340 memo: None,
3341 fee_payer: 0,
3342 custodian: None,
3343 no_wait: false,
3344 compute_unit_price: None,
3345 },
3346 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
3347 },
3348 );
3349 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3350 "test",
3351 "stake-authorize",
3352 &stake_account_string,
3353 "--new-withdraw-authority",
3354 &new_withdraw_string,
3355 "--withdraw-authority",
3356 &withdraw_authority_keypair_file,
3357 ]);
3358 assert_eq!(
3359 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3360 CliCommandInfo {
3361 command: CliCommand::StakeAuthorize {
3362 stake_account_pubkey,
3363 new_authorizations: vec![StakeAuthorizationIndexed {
3364 authorization_type: StakeAuthorize::Withdrawer,
3365 new_authority_pubkey: new_withdraw_authority,
3366 authority: 1,
3367 new_authority_signer: None,
3368 }],
3369 sign_only: false,
3370 dump_transaction_message: false,
3371 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3372 nonce_account: None,
3373 nonce_authority: 0,
3374 memo: None,
3375 fee_payer: 0,
3376 custodian: None,
3377 no_wait: false,
3378 compute_unit_price: None,
3379 },
3380 signers: vec![
3381 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3382 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3383 ],
3384 },
3385 );
3386
3387 let test_authorize = test_commands.clone().get_matches_from(vec![
3389 "test",
3390 "stake-authorize",
3391 &stake_account_string,
3392 "--new-stake-authority",
3393 &stake_account_string,
3394 "--no-wait",
3395 ]);
3396 assert_eq!(
3397 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3398 CliCommandInfo {
3399 command: CliCommand::StakeAuthorize {
3400 stake_account_pubkey,
3401 new_authorizations: vec![StakeAuthorizationIndexed {
3402 authorization_type: StakeAuthorize::Staker,
3403 new_authority_pubkey: stake_account_pubkey,
3404 authority: 0,
3405 new_authority_signer: None,
3406 }],
3407 sign_only: false,
3408 dump_transaction_message: false,
3409 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3410 nonce_account: None,
3411 nonce_authority: 0,
3412 memo: None,
3413 fee_payer: 0,
3414 custodian: None,
3415 no_wait: true,
3416 compute_unit_price: None,
3417 },
3418 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3419 }
3420 );
3421
3422 let (authority_keypair_file, mut tmp_file) = make_tmp_file();
3424 let authority_keypair = Keypair::new();
3425 write_keypair(&authority_keypair, tmp_file.as_file_mut()).unwrap();
3426 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3427 "test",
3428 "stake-authorize-checked",
3429 &stake_account_string,
3430 "--new-stake-authority",
3431 &authority_keypair_file,
3432 "--new-withdraw-authority",
3433 &authority_keypair_file,
3434 ]);
3435 assert_eq!(
3436 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3437 CliCommandInfo {
3438 command: CliCommand::StakeAuthorize {
3439 stake_account_pubkey,
3440 new_authorizations: vec![
3441 StakeAuthorizationIndexed {
3442 authorization_type: StakeAuthorize::Staker,
3443 new_authority_pubkey: authority_keypair.pubkey(),
3444 authority: 0,
3445 new_authority_signer: Some(1),
3446 },
3447 StakeAuthorizationIndexed {
3448 authorization_type: StakeAuthorize::Withdrawer,
3449 new_authority_pubkey: authority_keypair.pubkey(),
3450 authority: 0,
3451 new_authority_signer: Some(1),
3452 },
3453 ],
3454 sign_only: false,
3455 dump_transaction_message: false,
3456 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3457 nonce_account: None,
3458 nonce_authority: 0,
3459 memo: None,
3460 fee_payer: 0,
3461 custodian: None,
3462 no_wait: false,
3463 compute_unit_price: None,
3464 },
3465 signers: vec![
3466 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3467 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3468 ],
3469 },
3470 );
3471 let (withdraw_authority_keypair_file, mut tmp_file) = make_tmp_file();
3472 let withdraw_authority_keypair = Keypair::new();
3473 write_keypair(&withdraw_authority_keypair, tmp_file.as_file_mut()).unwrap();
3474 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3475 "test",
3476 "stake-authorize-checked",
3477 &stake_account_string,
3478 "--new-stake-authority",
3479 &authority_keypair_file,
3480 "--new-withdraw-authority",
3481 &authority_keypair_file,
3482 "--stake-authority",
3483 &stake_authority_keypair_file,
3484 "--withdraw-authority",
3485 &withdraw_authority_keypair_file,
3486 ]);
3487 assert_eq!(
3488 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3489 CliCommandInfo {
3490 command: CliCommand::StakeAuthorize {
3491 stake_account_pubkey,
3492 new_authorizations: vec![
3493 StakeAuthorizationIndexed {
3494 authorization_type: StakeAuthorize::Staker,
3495 new_authority_pubkey: authority_keypair.pubkey(),
3496 authority: 1,
3497 new_authority_signer: Some(2),
3498 },
3499 StakeAuthorizationIndexed {
3500 authorization_type: StakeAuthorize::Withdrawer,
3501 new_authority_pubkey: authority_keypair.pubkey(),
3502 authority: 3,
3503 new_authority_signer: Some(2),
3504 },
3505 ],
3506 sign_only: false,
3507 dump_transaction_message: false,
3508 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3509 nonce_account: None,
3510 nonce_authority: 0,
3511 memo: None,
3512 fee_payer: 0,
3513 custodian: None,
3514 no_wait: false,
3515 compute_unit_price: None,
3516 },
3517 signers: vec![
3518 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3519 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3520 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3521 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3522 ],
3523 },
3524 );
3525 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3527 "test",
3528 "stake-authorize-checked",
3529 &stake_account_string,
3530 "--new-stake-authority",
3531 &authority_keypair_file,
3532 "--new-withdraw-authority",
3533 &authority_keypair_file,
3534 "--withdraw-authority",
3535 &withdraw_authority_keypair_file,
3536 ]);
3537 assert_eq!(
3538 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3539 CliCommandInfo {
3540 command: CliCommand::StakeAuthorize {
3541 stake_account_pubkey,
3542 new_authorizations: vec![
3543 StakeAuthorizationIndexed {
3544 authorization_type: StakeAuthorize::Staker,
3545 new_authority_pubkey: authority_keypair.pubkey(),
3546 authority: 1,
3547 new_authority_signer: Some(2),
3548 },
3549 StakeAuthorizationIndexed {
3550 authorization_type: StakeAuthorize::Withdrawer,
3551 new_authority_pubkey: authority_keypair.pubkey(),
3552 authority: 1,
3553 new_authority_signer: Some(2),
3554 },
3555 ],
3556 sign_only: false,
3557 dump_transaction_message: false,
3558 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3559 nonce_account: None,
3560 nonce_authority: 0,
3561 memo: None,
3562 fee_payer: 0,
3563 custodian: None,
3564 no_wait: false,
3565 compute_unit_price: None,
3566 },
3567 signers: vec![
3568 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3569 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3570 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3571 ],
3572 },
3573 );
3574 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3575 "test",
3576 "stake-authorize-checked",
3577 &stake_account_string,
3578 "--new-stake-authority",
3579 &authority_keypair_file,
3580 ]);
3581 assert_eq!(
3582 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3583 CliCommandInfo {
3584 command: CliCommand::StakeAuthorize {
3585 stake_account_pubkey,
3586 new_authorizations: vec![StakeAuthorizationIndexed {
3587 authorization_type: StakeAuthorize::Staker,
3588 new_authority_pubkey: authority_keypair.pubkey(),
3589 authority: 0,
3590 new_authority_signer: Some(1),
3591 }],
3592 sign_only: false,
3593 dump_transaction_message: false,
3594 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3595 nonce_account: None,
3596 nonce_authority: 0,
3597 memo: None,
3598 fee_payer: 0,
3599 custodian: None,
3600 no_wait: false,
3601 compute_unit_price: None,
3602 },
3603 signers: vec![
3604 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3605 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3606 ],
3607 },
3608 );
3609 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3610 "test",
3611 "stake-authorize-checked",
3612 &stake_account_string,
3613 "--new-stake-authority",
3614 &authority_keypair_file,
3615 "--stake-authority",
3616 &stake_authority_keypair_file,
3617 ]);
3618 assert_eq!(
3619 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3620 CliCommandInfo {
3621 command: CliCommand::StakeAuthorize {
3622 stake_account_pubkey,
3623 new_authorizations: vec![StakeAuthorizationIndexed {
3624 authorization_type: StakeAuthorize::Staker,
3625 new_authority_pubkey: authority_keypair.pubkey(),
3626 authority: 1,
3627 new_authority_signer: Some(2),
3628 }],
3629 sign_only: false,
3630 dump_transaction_message: false,
3631 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3632 nonce_account: None,
3633 nonce_authority: 0,
3634 memo: None,
3635 fee_payer: 0,
3636 custodian: None,
3637 no_wait: false,
3638 compute_unit_price: None,
3639 },
3640 signers: vec![
3641 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3642 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
3643 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3644 ],
3645 },
3646 );
3647 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3649 "test",
3650 "stake-authorize-checked",
3651 &stake_account_string,
3652 "--new-stake-authority",
3653 &authority_keypair_file,
3654 "--withdraw-authority",
3655 &withdraw_authority_keypair_file,
3656 ]);
3657 assert_eq!(
3658 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3659 CliCommandInfo {
3660 command: CliCommand::StakeAuthorize {
3661 stake_account_pubkey,
3662 new_authorizations: vec![StakeAuthorizationIndexed {
3663 authorization_type: StakeAuthorize::Staker,
3664 new_authority_pubkey: authority_keypair.pubkey(),
3665 authority: 1,
3666 new_authority_signer: Some(2),
3667 }],
3668 sign_only: false,
3669 dump_transaction_message: false,
3670 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3671 nonce_account: None,
3672 nonce_authority: 0,
3673 memo: None,
3674 fee_payer: 0,
3675 custodian: None,
3676 no_wait: false,
3677 compute_unit_price: None,
3678 },
3679 signers: vec![
3680 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3681 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3682 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3683 ],
3684 },
3685 );
3686 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3687 "test",
3688 "stake-authorize-checked",
3689 &stake_account_string,
3690 "--new-withdraw-authority",
3691 &authority_keypair_file,
3692 ]);
3693 assert_eq!(
3694 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3695 CliCommandInfo {
3696 command: CliCommand::StakeAuthorize {
3697 stake_account_pubkey,
3698 new_authorizations: vec![StakeAuthorizationIndexed {
3699 authorization_type: StakeAuthorize::Withdrawer,
3700 new_authority_pubkey: authority_keypair.pubkey(),
3701 authority: 0,
3702 new_authority_signer: Some(1),
3703 }],
3704 sign_only: false,
3705 dump_transaction_message: false,
3706 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3707 nonce_account: None,
3708 nonce_authority: 0,
3709 memo: None,
3710 fee_payer: 0,
3711 custodian: None,
3712 no_wait: false,
3713 compute_unit_price: None,
3714 },
3715 signers: vec![
3716 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3717 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3718 ],
3719 },
3720 );
3721 let test_stake_authorize = test_commands.clone().get_matches_from(vec![
3722 "test",
3723 "stake-authorize-checked",
3724 &stake_account_string,
3725 "--new-withdraw-authority",
3726 &authority_keypair_file,
3727 "--withdraw-authority",
3728 &withdraw_authority_keypair_file,
3729 ]);
3730 assert_eq!(
3731 parse_command(&test_stake_authorize, &default_signer, &mut None).unwrap(),
3732 CliCommandInfo {
3733 command: CliCommand::StakeAuthorize {
3734 stake_account_pubkey,
3735 new_authorizations: vec![StakeAuthorizationIndexed {
3736 authorization_type: StakeAuthorize::Withdrawer,
3737 new_authority_pubkey: authority_keypair.pubkey(),
3738 authority: 1,
3739 new_authority_signer: Some(2),
3740 }],
3741 sign_only: false,
3742 dump_transaction_message: false,
3743 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3744 nonce_account: None,
3745 nonce_authority: 0,
3746 memo: None,
3747 fee_payer: 0,
3748 custodian: None,
3749 no_wait: false,
3750 compute_unit_price: None,
3751 },
3752 signers: vec![
3753 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3754 Box::new(read_keypair_file(&withdraw_authority_keypair_file).unwrap()),
3755 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3756 ],
3757 },
3758 );
3759
3760 let test_authorize = test_commands.clone().get_matches_from(vec![
3762 "test",
3763 "stake-authorize-checked",
3764 &stake_account_string,
3765 "--new-stake-authority",
3766 &authority_keypair_file,
3767 "--no-wait",
3768 ]);
3769 assert_eq!(
3770 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3771 CliCommandInfo {
3772 command: CliCommand::StakeAuthorize {
3773 stake_account_pubkey,
3774 new_authorizations: vec![StakeAuthorizationIndexed {
3775 authorization_type: StakeAuthorize::Staker,
3776 new_authority_pubkey: authority_keypair.pubkey(),
3777 authority: 0,
3778 new_authority_signer: Some(1),
3779 }],
3780 sign_only: false,
3781 dump_transaction_message: false,
3782 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
3783 nonce_account: None,
3784 nonce_authority: 0,
3785 memo: None,
3786 fee_payer: 0,
3787 custodian: None,
3788 no_wait: true,
3789 compute_unit_price: None,
3790 },
3791 signers: vec![
3792 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3793 Box::new(read_keypair_file(&authority_keypair_file).unwrap()),
3794 ],
3795 }
3796 );
3797
3798 let blockhash = Hash::default();
3800 let blockhash_string = format!("{blockhash}");
3801 let test_authorize = test_commands.clone().get_matches_from(vec![
3802 "test",
3803 "stake-authorize",
3804 &stake_account_string,
3805 "--new-stake-authority",
3806 &stake_account_string,
3807 "--blockhash",
3808 &blockhash_string,
3809 "--sign-only",
3810 ]);
3811 assert_eq!(
3812 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3813 CliCommandInfo {
3814 command: CliCommand::StakeAuthorize {
3815 stake_account_pubkey,
3816 new_authorizations: vec![StakeAuthorizationIndexed {
3817 authorization_type: StakeAuthorize::Staker,
3818 new_authority_pubkey: stake_account_pubkey,
3819 authority: 0,
3820 new_authority_signer: None,
3821 }],
3822 sign_only: true,
3823 dump_transaction_message: false,
3824 blockhash_query: BlockhashQuery::Static(blockhash),
3825 nonce_account: None,
3826 nonce_authority: 0,
3827 memo: None,
3828 fee_payer: 0,
3829 custodian: None,
3830 no_wait: false,
3831 compute_unit_price: None,
3832 },
3833 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3834 }
3835 );
3836 let keypair = Keypair::new();
3838 let pubkey = keypair.pubkey();
3839 let sig = keypair.sign_message(&[0u8]);
3840 let signer = format!("{}={}", keypair.pubkey(), sig);
3841 let test_authorize = test_commands.clone().get_matches_from(vec![
3842 "test",
3843 "stake-authorize",
3844 &stake_account_string,
3845 "--new-stake-authority",
3846 &stake_account_string,
3847 "--blockhash",
3848 &blockhash_string,
3849 "--signer",
3850 &signer,
3851 "--fee-payer",
3852 &pubkey.to_string(),
3853 ]);
3854 assert_eq!(
3855 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3856 CliCommandInfo {
3857 command: CliCommand::StakeAuthorize {
3858 stake_account_pubkey,
3859 new_authorizations: vec![StakeAuthorizationIndexed {
3860 authorization_type: StakeAuthorize::Staker,
3861 new_authority_pubkey: stake_account_pubkey,
3862 authority: 0,
3863 new_authority_signer: None,
3864 }],
3865 sign_only: false,
3866 dump_transaction_message: false,
3867 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3868 nonce_account: None,
3869 nonce_authority: 0,
3870 memo: None,
3871 fee_payer: 1,
3872 custodian: None,
3873 no_wait: false,
3874 compute_unit_price: None,
3875 },
3876 signers: vec![
3877 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3878 Box::new(Presigner::new(&pubkey, &sig))
3879 ],
3880 }
3881 );
3882 let keypair2 = Keypair::new();
3884 let pubkey2 = keypair2.pubkey();
3885 let sig2 = keypair.sign_message(&[0u8]);
3886 let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
3887 let nonce_account = Pubkey::from([1u8; 32]);
3888 let test_authorize = test_commands.clone().get_matches_from(vec![
3889 "test",
3890 "stake-authorize",
3891 &stake_account_string,
3892 "--new-stake-authority",
3893 &stake_account_string,
3894 "--blockhash",
3895 &blockhash_string,
3896 "--signer",
3897 &signer,
3898 "--signer",
3899 &signer2,
3900 "--fee-payer",
3901 &pubkey.to_string(),
3902 "--nonce",
3903 &nonce_account.to_string(),
3904 "--nonce-authority",
3905 &pubkey2.to_string(),
3906 ]);
3907 assert_eq!(
3908 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3909 CliCommandInfo {
3910 command: CliCommand::StakeAuthorize {
3911 stake_account_pubkey,
3912 new_authorizations: vec![StakeAuthorizationIndexed {
3913 authorization_type: StakeAuthorize::Staker,
3914 new_authority_pubkey: stake_account_pubkey,
3915 authority: 0,
3916 new_authority_signer: None,
3917 }],
3918 sign_only: false,
3919 dump_transaction_message: false,
3920 blockhash_query: BlockhashQuery::Validated(
3921 Source::NonceAccount(nonce_account),
3922 blockhash
3923 ),
3924 nonce_account: Some(nonce_account),
3925 nonce_authority: 2,
3926 memo: None,
3927 fee_payer: 1,
3928 custodian: None,
3929 no_wait: false,
3930 compute_unit_price: None,
3931 },
3932 signers: vec![
3933 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
3934 Box::new(Presigner::new(&pubkey, &sig)),
3935 Box::new(Presigner::new(&pubkey2, &sig2)),
3936 ],
3937 }
3938 );
3939 let test_authorize = test_commands.clone().get_matches_from(vec![
3941 "test",
3942 "stake-authorize",
3943 &stake_account_string,
3944 "--new-stake-authority",
3945 &stake_account_string,
3946 "--blockhash",
3947 &blockhash_string,
3948 ]);
3949 assert_eq!(
3950 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3951 CliCommandInfo {
3952 command: CliCommand::StakeAuthorize {
3953 stake_account_pubkey,
3954 new_authorizations: vec![StakeAuthorizationIndexed {
3955 authorization_type: StakeAuthorize::Staker,
3956 new_authority_pubkey: stake_account_pubkey,
3957 authority: 0,
3958 new_authority_signer: None,
3959 }],
3960 sign_only: false,
3961 dump_transaction_message: false,
3962 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
3963 nonce_account: None,
3964 nonce_authority: 0,
3965 memo: None,
3966 fee_payer: 0,
3967 custodian: None,
3968 no_wait: false,
3969 compute_unit_price: None,
3970 },
3971 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
3972 }
3973 );
3974 let (nonce_keypair_file, mut nonce_tmp_file) = make_tmp_file();
3976 let nonce_authority_keypair = Keypair::new();
3977 write_keypair(&nonce_authority_keypair, nonce_tmp_file.as_file_mut()).unwrap();
3978 let nonce_account_pubkey = nonce_authority_keypair.pubkey();
3979 let nonce_account_string = nonce_account_pubkey.to_string();
3980 let test_authorize = test_commands.clone().get_matches_from(vec![
3981 "test",
3982 "stake-authorize",
3983 &stake_account_string,
3984 "--new-stake-authority",
3985 &stake_account_string,
3986 "--blockhash",
3987 &blockhash_string,
3988 "--nonce",
3989 &nonce_account_string,
3990 "--nonce-authority",
3991 &nonce_keypair_file,
3992 ]);
3993 assert_eq!(
3994 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
3995 CliCommandInfo {
3996 command: CliCommand::StakeAuthorize {
3997 stake_account_pubkey,
3998 new_authorizations: vec![StakeAuthorizationIndexed {
3999 authorization_type: StakeAuthorize::Staker,
4000 new_authority_pubkey: stake_account_pubkey,
4001 authority: 0,
4002 new_authority_signer: None,
4003 }],
4004 sign_only: false,
4005 dump_transaction_message: false,
4006 blockhash_query: BlockhashQuery::Validated(
4007 Source::NonceAccount(nonce_account_pubkey),
4008 blockhash
4009 ),
4010 nonce_account: Some(nonce_account_pubkey),
4011 nonce_authority: 1,
4012 memo: None,
4013 fee_payer: 0,
4014 custodian: None,
4015 no_wait: false,
4016 compute_unit_price: None,
4017 },
4018 signers: vec![
4019 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4020 Box::new(nonce_authority_keypair)
4021 ],
4022 }
4023 );
4024 let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4026 let fee_payer_keypair = Keypair::new();
4027 write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4028 let fee_payer_pubkey = fee_payer_keypair.pubkey();
4029 let fee_payer_string = fee_payer_pubkey.to_string();
4030 let test_authorize = test_commands.clone().get_matches_from(vec![
4031 "test",
4032 "stake-authorize",
4033 &stake_account_string,
4034 "--new-stake-authority",
4035 &stake_account_string,
4036 "--fee-payer",
4037 &fee_payer_keypair_file,
4038 ]);
4039 assert_eq!(
4040 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4041 CliCommandInfo {
4042 command: CliCommand::StakeAuthorize {
4043 stake_account_pubkey,
4044 new_authorizations: vec![StakeAuthorizationIndexed {
4045 authorization_type: StakeAuthorize::Staker,
4046 new_authority_pubkey: stake_account_pubkey,
4047 authority: 0,
4048 new_authority_signer: None,
4049 }],
4050 sign_only: false,
4051 dump_transaction_message: false,
4052 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4053 nonce_account: None,
4054 nonce_authority: 0,
4055 memo: None,
4056 fee_payer: 1,
4057 custodian: None,
4058 no_wait: false,
4059 compute_unit_price: None,
4060 },
4061 signers: vec![
4062 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4063 Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap()),
4064 ],
4065 }
4066 );
4067 let sig = fee_payer_keypair.sign_message(&[0u8]);
4069 let signer = format!("{fee_payer_string}={sig}");
4070 let test_authorize = test_commands.clone().get_matches_from(vec![
4071 "test",
4072 "stake-authorize",
4073 &stake_account_string,
4074 "--new-stake-authority",
4075 &stake_account_string,
4076 "--fee-payer",
4077 &fee_payer_string,
4078 "--blockhash",
4079 &blockhash_string,
4080 "--signer",
4081 &signer,
4082 ]);
4083 assert_eq!(
4084 parse_command(&test_authorize, &default_signer, &mut None).unwrap(),
4085 CliCommandInfo {
4086 command: CliCommand::StakeAuthorize {
4087 stake_account_pubkey,
4088 new_authorizations: vec![StakeAuthorizationIndexed {
4089 authorization_type: StakeAuthorize::Staker,
4090 new_authority_pubkey: stake_account_pubkey,
4091 authority: 0,
4092 new_authority_signer: None,
4093 }],
4094 sign_only: false,
4095 dump_transaction_message: false,
4096 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4097 nonce_account: None,
4098 nonce_authority: 0,
4099 memo: None,
4100 fee_payer: 1,
4101 custodian: None,
4102 no_wait: false,
4103 compute_unit_price: None,
4104 },
4105 signers: vec![
4106 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4107 Box::new(Presigner::new(&fee_payer_pubkey, &sig))
4108 ],
4109 }
4110 );
4111
4112 let custodian = solana_pubkey::new_rand();
4114 let custodian_string = format!("{custodian}");
4115 let authorized = solana_pubkey::new_rand();
4116 let authorized_string = format!("{authorized}");
4117 let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4118 "test",
4119 "create-stake-account",
4120 &keypair_file,
4121 "50",
4122 "--stake-authority",
4123 &authorized_string,
4124 "--withdraw-authority",
4125 &authorized_string,
4126 "--custodian",
4127 &custodian_string,
4128 "--lockup-epoch",
4129 "43",
4130 ]);
4131 assert_eq!(
4132 parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4133 CliCommandInfo {
4134 command: CliCommand::CreateStakeAccount {
4135 stake_account: 1,
4136 seed: None,
4137 staker: Some(authorized),
4138 withdrawer: Some(authorized),
4139 withdrawer_signer: None,
4140 lockup: Lockup {
4141 epoch: 43,
4142 unix_timestamp: 0,
4143 custodian,
4144 },
4145 amount: SpendAmount::Some(50_000_000_000),
4146 sign_only: false,
4147 dump_transaction_message: false,
4148 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4149 nonce_account: None,
4150 nonce_authority: 0,
4151 memo: None,
4152 fee_payer: 0,
4153 from: 0,
4154 compute_unit_price: None,
4155 },
4156 signers: vec![
4157 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4158 Box::new(stake_account_keypair)
4159 ],
4160 }
4161 );
4162
4163 let (keypair_file, mut tmp_file) = make_tmp_file();
4164 let stake_account_keypair = Keypair::new();
4165 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
4166 let stake_account_pubkey = stake_account_keypair.pubkey();
4167 let stake_account_string = stake_account_pubkey.to_string();
4168
4169 let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4170 "test",
4171 "create-stake-account",
4172 &keypair_file,
4173 "50",
4174 ]);
4175
4176 assert_eq!(
4177 parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4178 CliCommandInfo {
4179 command: CliCommand::CreateStakeAccount {
4180 stake_account: 1,
4181 seed: None,
4182 staker: None,
4183 withdrawer: None,
4184 withdrawer_signer: None,
4185 lockup: Lockup::default(),
4186 amount: SpendAmount::Some(50_000_000_000),
4187 sign_only: false,
4188 dump_transaction_message: false,
4189 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4190 nonce_account: None,
4191 nonce_authority: 0,
4192 memo: None,
4193 fee_payer: 0,
4194 from: 0,
4195 compute_unit_price: None,
4196 },
4197 signers: vec![
4198 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4199 Box::new(read_keypair_file(&keypair_file).unwrap())
4200 ],
4201 }
4202 );
4203 let (withdrawer_keypair_file, mut tmp_file) = make_tmp_file();
4204 let withdrawer_keypair = Keypair::new();
4205 write_keypair(&withdrawer_keypair, tmp_file.as_file_mut()).unwrap();
4206 let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4207 "test",
4208 "create-stake-account-checked",
4209 &keypair_file,
4210 "50",
4211 "--stake-authority",
4212 &authorized_string,
4213 "--withdraw-authority",
4214 &withdrawer_keypair_file,
4215 ]);
4216 assert_eq!(
4217 parse_command(&test_create_stake_account, &default_signer, &mut None).unwrap(),
4218 CliCommandInfo {
4219 command: CliCommand::CreateStakeAccount {
4220 stake_account: 1,
4221 seed: None,
4222 staker: Some(authorized),
4223 withdrawer: Some(withdrawer_keypair.pubkey()),
4224 withdrawer_signer: Some(2),
4225 lockup: Lockup::default(),
4226 amount: SpendAmount::Some(50_000_000_000),
4227 sign_only: false,
4228 dump_transaction_message: false,
4229 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4230 nonce_account: None,
4231 nonce_authority: 0,
4232 memo: None,
4233 fee_payer: 0,
4234 from: 0,
4235 compute_unit_price: None,
4236 },
4237 signers: vec![
4238 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4239 Box::new(stake_account_keypair),
4240 Box::new(withdrawer_keypair),
4241 ],
4242 }
4243 );
4244
4245 let test_create_stake_account = test_commands.clone().get_matches_from(vec![
4246 "test",
4247 "create-stake-account-checked",
4248 &keypair_file,
4249 "50",
4250 "--stake-authority",
4251 &authorized_string,
4252 "--withdraw-authority",
4253 &authorized_string,
4254 ]);
4255 assert!(parse_command(&test_create_stake_account, &default_signer, &mut None).is_err());
4256
4257 let nonce_account = Pubkey::from([1u8; 32]);
4259 let nonce_account_string = nonce_account.to_string();
4260 let offline = keypair_from_seed(&[2u8; 32]).unwrap();
4261 let offline_pubkey = offline.pubkey();
4262 let offline_string = offline_pubkey.to_string();
4263 let offline_sig = offline.sign_message(&[3u8]);
4264 let offline_signer = format!("{offline_pubkey}={offline_sig}");
4265 let nonce_hash = Hash::new_from_array([4u8; 32]);
4266 let nonce_hash_string = nonce_hash.to_string();
4267 let test_create_stake_account2 = test_commands.clone().get_matches_from(vec![
4268 "test",
4269 "create-stake-account",
4270 &keypair_file,
4271 "50",
4272 "--blockhash",
4273 &nonce_hash_string,
4274 "--nonce",
4275 &nonce_account_string,
4276 "--nonce-authority",
4277 &offline_string,
4278 "--fee-payer",
4279 &offline_string,
4280 "--from",
4281 &offline_string,
4282 "--signer",
4283 &offline_signer,
4284 ]);
4285
4286 assert_eq!(
4287 parse_command(&test_create_stake_account2, &default_signer, &mut None).unwrap(),
4288 CliCommandInfo {
4289 command: CliCommand::CreateStakeAccount {
4290 stake_account: 1,
4291 seed: None,
4292 staker: None,
4293 withdrawer: None,
4294 withdrawer_signer: None,
4295 lockup: Lockup::default(),
4296 amount: SpendAmount::Some(50_000_000_000),
4297 sign_only: false,
4298 dump_transaction_message: false,
4299 blockhash_query: BlockhashQuery::Validated(
4300 Source::NonceAccount(nonce_account),
4301 nonce_hash
4302 ),
4303 nonce_account: Some(nonce_account),
4304 nonce_authority: 0,
4305 memo: None,
4306 fee_payer: 0,
4307 from: 0,
4308 compute_unit_price: None,
4309 },
4310 signers: vec![
4311 Box::new(Presigner::new(&offline_pubkey, &offline_sig)),
4312 Box::new(read_keypair_file(&keypair_file).unwrap())
4313 ],
4314 }
4315 );
4316
4317 let vote_account_pubkey = solana_pubkey::new_rand();
4319 let vote_account_string = vote_account_pubkey.to_string();
4320 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4321 "test",
4322 "delegate-stake",
4323 &stake_account_string,
4324 &vote_account_string,
4325 ]);
4326 assert_eq!(
4327 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4328 CliCommandInfo {
4329 command: CliCommand::DelegateStake {
4330 stake_account_pubkey,
4331 vote_account_pubkey,
4332 stake_authority: 0,
4333 force: false,
4334 sign_only: false,
4335 dump_transaction_message: false,
4336 blockhash_query: BlockhashQuery::default(),
4337 nonce_account: None,
4338 nonce_authority: 0,
4339 memo: None,
4340 fee_payer: 0,
4341 compute_unit_price: None,
4342 },
4343 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4344 }
4345 );
4346
4347 let vote_account_pubkey = solana_pubkey::new_rand();
4349 let vote_account_string = vote_account_pubkey.to_string();
4350 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4351 "test",
4352 "delegate-stake",
4353 &stake_account_string,
4354 &vote_account_string,
4355 "--stake-authority",
4356 &stake_authority_keypair_file,
4357 ]);
4358 assert_eq!(
4359 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4360 CliCommandInfo {
4361 command: CliCommand::DelegateStake {
4362 stake_account_pubkey,
4363 vote_account_pubkey,
4364 stake_authority: 1,
4365 force: false,
4366 sign_only: false,
4367 dump_transaction_message: false,
4368 blockhash_query: BlockhashQuery::default(),
4369 nonce_account: None,
4370 nonce_authority: 0,
4371 memo: None,
4372 fee_payer: 0,
4373 compute_unit_price: None,
4374 },
4375 signers: vec![
4376 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4377 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4378 ],
4379 }
4380 );
4381
4382 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4384 "test",
4385 "delegate-stake",
4386 "--force",
4387 &stake_account_string,
4388 &vote_account_string,
4389 ]);
4390 assert_eq!(
4391 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4392 CliCommandInfo {
4393 command: CliCommand::DelegateStake {
4394 stake_account_pubkey,
4395 vote_account_pubkey,
4396 stake_authority: 0,
4397 force: true,
4398 sign_only: false,
4399 dump_transaction_message: false,
4400 blockhash_query: BlockhashQuery::default(),
4401 nonce_account: None,
4402 nonce_authority: 0,
4403 memo: None,
4404 fee_payer: 0,
4405 compute_unit_price: None,
4406 },
4407 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4408 }
4409 );
4410
4411 let blockhash = Hash::default();
4413 let blockhash_string = format!("{blockhash}");
4414 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4415 "test",
4416 "delegate-stake",
4417 &stake_account_string,
4418 &vote_account_string,
4419 "--blockhash",
4420 &blockhash_string,
4421 ]);
4422 assert_eq!(
4423 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4424 CliCommandInfo {
4425 command: CliCommand::DelegateStake {
4426 stake_account_pubkey,
4427 vote_account_pubkey,
4428 stake_authority: 0,
4429 force: false,
4430 sign_only: false,
4431 dump_transaction_message: false,
4432 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4433 nonce_account: None,
4434 nonce_authority: 0,
4435 memo: None,
4436 fee_payer: 0,
4437 compute_unit_price: None,
4438 },
4439 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4440 }
4441 );
4442
4443 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4444 "test",
4445 "delegate-stake",
4446 &stake_account_string,
4447 &vote_account_string,
4448 "--blockhash",
4449 &blockhash_string,
4450 "--sign-only",
4451 ]);
4452 assert_eq!(
4453 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4454 CliCommandInfo {
4455 command: CliCommand::DelegateStake {
4456 stake_account_pubkey,
4457 vote_account_pubkey,
4458 stake_authority: 0,
4459 force: false,
4460 sign_only: true,
4461 dump_transaction_message: false,
4462 blockhash_query: BlockhashQuery::Static(blockhash),
4463 nonce_account: None,
4464 nonce_authority: 0,
4465 memo: None,
4466 fee_payer: 0,
4467 compute_unit_price: None,
4468 },
4469 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4470 }
4471 );
4472
4473 let key1 = solana_pubkey::new_rand();
4475 let sig1 = Keypair::new().sign_message(&[0u8]);
4476 let signer1 = format!("{key1}={sig1}");
4477 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4478 "test",
4479 "delegate-stake",
4480 &stake_account_string,
4481 &vote_account_string,
4482 "--blockhash",
4483 &blockhash_string,
4484 "--signer",
4485 &signer1,
4486 "--fee-payer",
4487 &key1.to_string(),
4488 ]);
4489 assert_eq!(
4490 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4491 CliCommandInfo {
4492 command: CliCommand::DelegateStake {
4493 stake_account_pubkey,
4494 vote_account_pubkey,
4495 stake_authority: 0,
4496 force: false,
4497 sign_only: false,
4498 dump_transaction_message: false,
4499 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4500 nonce_account: None,
4501 nonce_authority: 0,
4502 memo: None,
4503 fee_payer: 1,
4504 compute_unit_price: None,
4505 },
4506 signers: vec![
4507 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4508 Box::new(Presigner::new(&key1, &sig1))
4509 ],
4510 }
4511 );
4512
4513 let key2 = solana_pubkey::new_rand();
4515 let sig2 = Keypair::new().sign_message(&[0u8]);
4516 let signer2 = format!("{key2}={sig2}");
4517 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4518 "test",
4519 "delegate-stake",
4520 &stake_account_string,
4521 &vote_account_string,
4522 "--blockhash",
4523 &blockhash_string,
4524 "--signer",
4525 &signer1,
4526 "--signer",
4527 &signer2,
4528 "--fee-payer",
4529 &key1.to_string(),
4530 "--nonce",
4531 &nonce_account.to_string(),
4532 "--nonce-authority",
4533 &key2.to_string(),
4534 ]);
4535 assert_eq!(
4536 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4537 CliCommandInfo {
4538 command: CliCommand::DelegateStake {
4539 stake_account_pubkey,
4540 vote_account_pubkey,
4541 stake_authority: 0,
4542 force: false,
4543 sign_only: false,
4544 dump_transaction_message: false,
4545 blockhash_query: BlockhashQuery::Validated(
4546 Source::NonceAccount(nonce_account),
4547 blockhash
4548 ),
4549 nonce_account: Some(nonce_account),
4550 nonce_authority: 2,
4551 memo: None,
4552 fee_payer: 1,
4553 compute_unit_price: None,
4554 },
4555 signers: vec![
4556 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4557 Box::new(Presigner::new(&key1, &sig1)),
4558 Box::new(Presigner::new(&key2, &sig2)),
4559 ],
4560 }
4561 );
4562
4563 let (fee_payer_keypair_file, mut fee_payer_tmp_file) = make_tmp_file();
4565 let fee_payer_keypair = Keypair::new();
4566 write_keypair(&fee_payer_keypair, fee_payer_tmp_file.as_file_mut()).unwrap();
4567 let test_delegate_stake = test_commands.clone().get_matches_from(vec![
4568 "test",
4569 "delegate-stake",
4570 &stake_account_string,
4571 &vote_account_string,
4572 "--fee-payer",
4573 &fee_payer_keypair_file,
4574 ]);
4575 assert_eq!(
4576 parse_command(&test_delegate_stake, &default_signer, &mut None).unwrap(),
4577 CliCommandInfo {
4578 command: CliCommand::DelegateStake {
4579 stake_account_pubkey,
4580 vote_account_pubkey,
4581 stake_authority: 0,
4582 force: false,
4583 sign_only: false,
4584 dump_transaction_message: false,
4585 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4586 nonce_account: None,
4587 nonce_authority: 0,
4588 memo: None,
4589 fee_payer: 1,
4590 compute_unit_price: None,
4591 },
4592 signers: vec![
4593 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4594 Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
4595 ],
4596 }
4597 );
4598
4599 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4601 "test",
4602 "withdraw-stake",
4603 &stake_account_string,
4604 &stake_account_string,
4605 "42",
4606 ]);
4607
4608 assert_eq!(
4609 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4610 CliCommandInfo {
4611 command: CliCommand::WithdrawStake {
4612 stake_account_pubkey,
4613 destination_account_pubkey: stake_account_pubkey,
4614 amount: SpendAmount::Some(42_000_000_000),
4615 withdraw_authority: 0,
4616 custodian: None,
4617 sign_only: false,
4618 dump_transaction_message: false,
4619 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4620 nonce_account: None,
4621 nonce_authority: 0,
4622 memo: None,
4623 seed: None,
4624 fee_payer: 0,
4625 compute_unit_price: None,
4626 },
4627 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4628 }
4629 );
4630
4631 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4633 "test",
4634 "withdraw-stake",
4635 &stake_account_string,
4636 &stake_account_string,
4637 "AVAILABLE",
4638 ]);
4639
4640 assert_eq!(
4641 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4642 CliCommandInfo {
4643 command: CliCommand::WithdrawStake {
4644 stake_account_pubkey,
4645 destination_account_pubkey: stake_account_pubkey,
4646 amount: SpendAmount::Available,
4647 withdraw_authority: 0,
4648 custodian: None,
4649 sign_only: false,
4650 dump_transaction_message: false,
4651 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4652 nonce_account: None,
4653 nonce_authority: 0,
4654 memo: None,
4655 seed: None,
4656 fee_payer: 0,
4657 compute_unit_price: None,
4658 },
4659 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4660 }
4661 );
4662
4663 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4665 "test",
4666 "withdraw-stake",
4667 &stake_account_string,
4668 &stake_account_string,
4669 "42",
4670 "--with-compute-unit-price",
4671 "99",
4672 ]);
4673
4674 assert_eq!(
4675 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4676 CliCommandInfo {
4677 command: CliCommand::WithdrawStake {
4678 stake_account_pubkey,
4679 destination_account_pubkey: stake_account_pubkey,
4680 amount: SpendAmount::Some(42_000_000_000),
4681 withdraw_authority: 0,
4682 custodian: None,
4683 sign_only: false,
4684 dump_transaction_message: false,
4685 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4686 nonce_account: None,
4687 nonce_authority: 0,
4688 memo: None,
4689 seed: None,
4690 fee_payer: 0,
4691 compute_unit_price: Some(99),
4692 },
4693 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4694 }
4695 );
4696
4697 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4699 "test",
4700 "withdraw-stake",
4701 &stake_account_string,
4702 &stake_account_string,
4703 "42",
4704 "--withdraw-authority",
4705 &stake_authority_keypair_file,
4706 ]);
4707
4708 assert_eq!(
4709 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4710 CliCommandInfo {
4711 command: CliCommand::WithdrawStake {
4712 stake_account_pubkey,
4713 destination_account_pubkey: stake_account_pubkey,
4714 amount: SpendAmount::Some(42_000_000_000),
4715 withdraw_authority: 1,
4716 custodian: None,
4717 sign_only: false,
4718 dump_transaction_message: false,
4719 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4720 nonce_account: None,
4721 nonce_authority: 0,
4722 memo: None,
4723 seed: None,
4724 fee_payer: 0,
4725 compute_unit_price: None,
4726 },
4727 signers: vec![
4728 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4729 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4730 ],
4731 }
4732 );
4733
4734 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4736 "test",
4737 "withdraw-stake",
4738 &stake_account_string,
4739 &stake_account_string,
4740 "42",
4741 "--custodian",
4742 &custodian_keypair_file,
4743 ]);
4744
4745 assert_eq!(
4746 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4747 CliCommandInfo {
4748 command: CliCommand::WithdrawStake {
4749 stake_account_pubkey,
4750 destination_account_pubkey: stake_account_pubkey,
4751 amount: SpendAmount::Some(42_000_000_000),
4752 withdraw_authority: 0,
4753 custodian: Some(1),
4754 sign_only: false,
4755 dump_transaction_message: false,
4756 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
4757 nonce_account: None,
4758 nonce_authority: 0,
4759 memo: None,
4760 seed: None,
4761 fee_payer: 0,
4762 compute_unit_price: None,
4763 },
4764 signers: vec![
4765 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4766 Box::new(read_keypair_file(&custodian_keypair_file).unwrap())
4767 ],
4768 }
4769 );
4770
4771 let test_withdraw_stake = test_commands.clone().get_matches_from(vec![
4773 "test",
4774 "withdraw-stake",
4775 &stake_account_string,
4776 &stake_account_string,
4777 "42",
4778 "--withdraw-authority",
4779 &stake_authority_keypair_file,
4780 "--blockhash",
4781 &nonce_hash_string,
4782 "--nonce",
4783 &nonce_account_string,
4784 "--nonce-authority",
4785 &offline_string,
4786 "--fee-payer",
4787 &offline_string,
4788 "--signer",
4789 &offline_signer,
4790 ]);
4791
4792 assert_eq!(
4793 parse_command(&test_withdraw_stake, &default_signer, &mut None).unwrap(),
4794 CliCommandInfo {
4795 command: CliCommand::WithdrawStake {
4796 stake_account_pubkey,
4797 destination_account_pubkey: stake_account_pubkey,
4798 amount: SpendAmount::Some(42_000_000_000),
4799 withdraw_authority: 0,
4800 custodian: None,
4801 sign_only: false,
4802 dump_transaction_message: false,
4803 blockhash_query: BlockhashQuery::Validated(
4804 Source::NonceAccount(nonce_account),
4805 nonce_hash
4806 ),
4807 nonce_account: Some(nonce_account),
4808 nonce_authority: 1,
4809 memo: None,
4810 seed: None,
4811 fee_payer: 1,
4812 compute_unit_price: None,
4813 },
4814 signers: vec![
4815 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap()),
4816 Box::new(Presigner::new(&offline_pubkey, &offline_sig))
4817 ],
4818 }
4819 );
4820
4821 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4823 "test",
4824 "deactivate-stake",
4825 &stake_account_string,
4826 ]);
4827 assert_eq!(
4828 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4829 CliCommandInfo {
4830 command: CliCommand::DeactivateStake {
4831 stake_account_pubkey,
4832 stake_authority: 0,
4833 sign_only: false,
4834 deactivate_delinquent: false,
4835 dump_transaction_message: false,
4836 blockhash_query: BlockhashQuery::default(),
4837 nonce_account: None,
4838 nonce_authority: 0,
4839 memo: None,
4840 seed: None,
4841 fee_payer: 0,
4842 compute_unit_price: None,
4843 },
4844 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4845 }
4846 );
4847
4848 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4850 "test",
4851 "deactivate-stake",
4852 &stake_account_string,
4853 "--delinquent",
4854 ]);
4855 assert_eq!(
4856 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4857 CliCommandInfo {
4858 command: CliCommand::DeactivateStake {
4859 stake_account_pubkey,
4860 stake_authority: 0,
4861 sign_only: false,
4862 deactivate_delinquent: true,
4863 dump_transaction_message: false,
4864 blockhash_query: BlockhashQuery::default(),
4865 nonce_account: None,
4866 nonce_authority: 0,
4867 memo: None,
4868 seed: None,
4869 fee_payer: 0,
4870 compute_unit_price: None,
4871 },
4872 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4873 }
4874 );
4875
4876 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4878 "test",
4879 "deactivate-stake",
4880 &stake_account_string,
4881 "--stake-authority",
4882 &stake_authority_keypair_file,
4883 ]);
4884 assert_eq!(
4885 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4886 CliCommandInfo {
4887 command: CliCommand::DeactivateStake {
4888 stake_account_pubkey,
4889 stake_authority: 1,
4890 sign_only: false,
4891 deactivate_delinquent: false,
4892 dump_transaction_message: false,
4893 blockhash_query: BlockhashQuery::default(),
4894 nonce_account: None,
4895 nonce_authority: 0,
4896 memo: None,
4897 seed: None,
4898 fee_payer: 0,
4899 compute_unit_price: None,
4900 },
4901 signers: vec![
4902 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
4903 Box::new(read_keypair_file(&stake_authority_keypair_file).unwrap())
4904 ],
4905 }
4906 );
4907
4908 let blockhash = Hash::default();
4910 let blockhash_string = format!("{blockhash}");
4911 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4912 "test",
4913 "deactivate-stake",
4914 &stake_account_string,
4915 "--blockhash",
4916 &blockhash_string,
4917 ]);
4918 assert_eq!(
4919 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4920 CliCommandInfo {
4921 command: CliCommand::DeactivateStake {
4922 stake_account_pubkey,
4923 stake_authority: 0,
4924 sign_only: false,
4925 deactivate_delinquent: false,
4926 dump_transaction_message: false,
4927 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4928 nonce_account: None,
4929 nonce_authority: 0,
4930 memo: None,
4931 seed: None,
4932 fee_payer: 0,
4933 compute_unit_price: None,
4934 },
4935 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4936 }
4937 );
4938
4939 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4940 "test",
4941 "deactivate-stake",
4942 &stake_account_string,
4943 "--blockhash",
4944 &blockhash_string,
4945 "--sign-only",
4946 ]);
4947 assert_eq!(
4948 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4949 CliCommandInfo {
4950 command: CliCommand::DeactivateStake {
4951 stake_account_pubkey,
4952 stake_authority: 0,
4953 sign_only: true,
4954 deactivate_delinquent: false,
4955 dump_transaction_message: false,
4956 blockhash_query: BlockhashQuery::Static(blockhash),
4957 nonce_account: None,
4958 nonce_authority: 0,
4959 memo: None,
4960 seed: None,
4961 fee_payer: 0,
4962 compute_unit_price: None,
4963 },
4964 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
4965 }
4966 );
4967
4968 let key1 = solana_pubkey::new_rand();
4970 let sig1 = Keypair::new().sign_message(&[0u8]);
4971 let signer1 = format!("{key1}={sig1}");
4972 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
4973 "test",
4974 "deactivate-stake",
4975 &stake_account_string,
4976 "--blockhash",
4977 &blockhash_string,
4978 "--signer",
4979 &signer1,
4980 "--fee-payer",
4981 &key1.to_string(),
4982 ]);
4983 assert_eq!(
4984 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
4985 CliCommandInfo {
4986 command: CliCommand::DeactivateStake {
4987 stake_account_pubkey,
4988 stake_authority: 0,
4989 sign_only: false,
4990 deactivate_delinquent: false,
4991 dump_transaction_message: false,
4992 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
4993 nonce_account: None,
4994 nonce_authority: 0,
4995 memo: None,
4996 seed: None,
4997 fee_payer: 1,
4998 compute_unit_price: None,
4999 },
5000 signers: vec![
5001 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5002 Box::new(Presigner::new(&key1, &sig1))
5003 ],
5004 }
5005 );
5006
5007 let key2 = solana_pubkey::new_rand();
5009 let sig2 = Keypair::new().sign_message(&[0u8]);
5010 let signer2 = format!("{key2}={sig2}");
5011 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5012 "test",
5013 "deactivate-stake",
5014 &stake_account_string,
5015 "--blockhash",
5016 &blockhash_string,
5017 "--signer",
5018 &signer1,
5019 "--signer",
5020 &signer2,
5021 "--fee-payer",
5022 &key1.to_string(),
5023 "--nonce",
5024 &nonce_account.to_string(),
5025 "--nonce-authority",
5026 &key2.to_string(),
5027 ]);
5028 assert_eq!(
5029 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5030 CliCommandInfo {
5031 command: CliCommand::DeactivateStake {
5032 stake_account_pubkey,
5033 stake_authority: 0,
5034 sign_only: false,
5035 deactivate_delinquent: false,
5036 dump_transaction_message: false,
5037 blockhash_query: BlockhashQuery::Validated(
5038 Source::NonceAccount(nonce_account),
5039 blockhash
5040 ),
5041 nonce_account: Some(nonce_account),
5042 nonce_authority: 2,
5043 memo: None,
5044 seed: None,
5045 fee_payer: 1,
5046 compute_unit_price: None,
5047 },
5048 signers: vec![
5049 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5050 Box::new(Presigner::new(&key1, &sig1)),
5051 Box::new(Presigner::new(&key2, &sig2)),
5052 ],
5053 }
5054 );
5055
5056 let test_deactivate_stake = test_commands.clone().get_matches_from(vec![
5058 "test",
5059 "deactivate-stake",
5060 &stake_account_string,
5061 "--fee-payer",
5062 &fee_payer_keypair_file,
5063 ]);
5064 assert_eq!(
5065 parse_command(&test_deactivate_stake, &default_signer, &mut None).unwrap(),
5066 CliCommandInfo {
5067 command: CliCommand::DeactivateStake {
5068 stake_account_pubkey,
5069 stake_authority: 0,
5070 sign_only: false,
5071 deactivate_delinquent: false,
5072 dump_transaction_message: false,
5073 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
5074 nonce_account: None,
5075 nonce_authority: 0,
5076 memo: None,
5077 seed: None,
5078 fee_payer: 1,
5079 compute_unit_price: None,
5080 },
5081 signers: vec![
5082 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5083 Box::new(read_keypair_file(&fee_payer_keypair_file).unwrap())
5084 ],
5085 }
5086 );
5087
5088 let (keypair_file, mut tmp_file) = make_tmp_file();
5090 let stake_account_keypair = Keypair::new();
5091 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5092 let (split_stake_account_keypair_file, mut tmp_file) = make_tmp_file();
5093 let split_stake_account_keypair = Keypair::new();
5094 write_keypair(&split_stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5095
5096 let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5097 "test",
5098 "split-stake",
5099 &keypair_file,
5100 &split_stake_account_keypair_file,
5101 "50",
5102 ]);
5103 assert_eq!(
5104 parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5105 CliCommandInfo {
5106 command: CliCommand::SplitStake {
5107 stake_account_pubkey: stake_account_keypair.pubkey(),
5108 stake_authority: 0,
5109 sign_only: false,
5110 dump_transaction_message: false,
5111 blockhash_query: BlockhashQuery::default(),
5112 nonce_account: None,
5113 nonce_authority: 0,
5114 memo: None,
5115 split_stake_account: 1,
5116 seed: None,
5117 lamports: 50_000_000_000,
5118 fee_payer: 0,
5119 compute_unit_price: None,
5120 rent_exempt_reserve: None,
5121 },
5122 signers: vec![
5123 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
5124 Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap())
5125 ],
5126 }
5127 );
5128
5129 let nonce_account = Pubkey::from([1u8; 32]);
5131 let nonce_account_string = nonce_account.to_string();
5132 let nonce_auth = keypair_from_seed(&[2u8; 32]).unwrap();
5133 let nonce_auth_pubkey = nonce_auth.pubkey();
5134 let nonce_auth_string = nonce_auth_pubkey.to_string();
5135 let nonce_sig = nonce_auth.sign_message(&[0u8]);
5136 let nonce_signer = format!("{nonce_auth_pubkey}={nonce_sig}");
5137 let stake_auth = keypair_from_seed(&[3u8; 32]).unwrap();
5138 let stake_auth_pubkey = stake_auth.pubkey();
5139 let stake_auth_string = stake_auth_pubkey.to_string();
5140 let stake_sig = stake_auth.sign_message(&[0u8]);
5141 let stake_signer = format!("{stake_auth_pubkey}={stake_sig}");
5142 let nonce_hash = Hash::new_from_array([4u8; 32]);
5143 let nonce_hash_string = nonce_hash.to_string();
5144
5145 let test_split_stake_account = test_commands.clone().get_matches_from(vec![
5146 "test",
5147 "split-stake",
5148 &keypair_file,
5149 &split_stake_account_keypair_file,
5150 "50",
5151 "--stake-authority",
5152 &stake_auth_string,
5153 "--blockhash",
5154 &nonce_hash_string,
5155 "--nonce",
5156 &nonce_account_string,
5157 "--nonce-authority",
5158 &nonce_auth_string,
5159 "--fee-payer",
5160 &nonce_auth_string, "--signer",
5162 &nonce_signer,
5163 "--signer",
5164 &stake_signer,
5165 ]);
5166 assert_eq!(
5167 parse_command(&test_split_stake_account, &default_signer, &mut None).unwrap(),
5168 CliCommandInfo {
5169 command: CliCommand::SplitStake {
5170 stake_account_pubkey: stake_account_keypair.pubkey(),
5171 stake_authority: 0,
5172 sign_only: false,
5173 dump_transaction_message: false,
5174 blockhash_query: BlockhashQuery::Validated(
5175 Source::NonceAccount(nonce_account),
5176 nonce_hash
5177 ),
5178 nonce_account: Some(nonce_account),
5179 nonce_authority: 1,
5180 memo: None,
5181 split_stake_account: 2,
5182 seed: None,
5183 lamports: 50_000_000_000,
5184 fee_payer: 1,
5185 compute_unit_price: None,
5186 rent_exempt_reserve: None,
5187 },
5188 signers: vec![
5189 Box::new(Presigner::new(&stake_auth_pubkey, &stake_sig)),
5190 Box::new(Presigner::new(&nonce_auth_pubkey, &nonce_sig)),
5191 Box::new(read_keypair_file(&split_stake_account_keypair_file).unwrap()),
5192 ],
5193 }
5194 );
5195
5196 let (keypair_file, mut tmp_file) = make_tmp_file();
5198 let stake_account_keypair = Keypair::new();
5199 write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap();
5200
5201 let source_stake_account_pubkey = solana_pubkey::new_rand();
5202 let test_merge_stake_account = test_commands.clone().get_matches_from(vec![
5203 "test",
5204 "merge-stake",
5205 &keypair_file,
5206 &source_stake_account_pubkey.to_string(),
5207 ]);
5208 assert_eq!(
5209 parse_command(&test_merge_stake_account, &default_signer, &mut None).unwrap(),
5210 CliCommandInfo {
5211 command: CliCommand::MergeStake {
5212 stake_account_pubkey: stake_account_keypair.pubkey(),
5213 source_stake_account_pubkey,
5214 stake_authority: 0,
5215 sign_only: false,
5216 dump_transaction_message: false,
5217 blockhash_query: BlockhashQuery::default(),
5218 nonce_account: None,
5219 nonce_authority: 0,
5220 memo: None,
5221 fee_payer: 0,
5222 compute_unit_price: None,
5223 },
5224 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap()),],
5225 }
5226 );
5227 }
5228}