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