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