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