1use {
2 crate::{
3 checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4 cli::{
5 CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult,
6 log_instruction_custom_error,
7 },
8 compute_budget::{
9 ComputeUnitConfig, WithComputeUnitConfig, simulate_and_update_compute_unit_limit,
10 },
11 feature::get_feature_is_active,
12 memo::WithMemo,
13 nonce::check_nonce_account,
14 spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balances},
15 stake::check_current_authority,
16 },
17 agave_feature_set::{bls_pubkey_management_in_vote_account, vote_account_initialize_v2},
18 agave_votor_messages::consensus_message::BLS_KEYPAIR_DERIVE_SEED,
19 clap::{App, Arg, ArgMatches, SubCommand, value_t_or_exit},
20 solana_account::Account,
21 solana_bls_signatures::keypair::Keypair as BLSKeypair,
22 solana_clap_utils::{
23 compute_budget::{COMPUTE_UNIT_PRICE_ARG, ComputeUnitLimit, compute_unit_price_arg},
24 fee_payer::{FEE_PAYER_ARG, fee_payer_arg},
25 input_parsers::*,
26 input_validators::*,
27 keypair::{DefaultSigner, SignerIndex},
28 memo::{MEMO_ARG, memo_arg},
29 nonce::*,
30 offline::*,
31 },
32 solana_cli_output::{
33 CliEpochVotingHistory, CliLandedVote, CliVoteAccount, ReturnSignersConfig,
34 display::build_balance_message, return_signers_with_config,
35 },
36 solana_commitment_config::CommitmentConfig,
37 solana_feature_gate_interface::from_account,
38 solana_message::Message,
39 solana_pubkey::Pubkey,
40 solana_remote_wallet::remote_wallet::RemoteWalletManager,
41 solana_rpc_client::nonblocking::rpc_client::RpcClient,
42 solana_rpc_client_api::config::RpcGetVoteAccountsConfig,
43 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::BlockhashQuery,
44 solana_system_interface::error::SystemError,
45 solana_transaction::Transaction,
46 solana_vote_program::{
47 vote_error::VoteError,
48 vote_instruction::{self, CreateVoteAccountConfig, withdraw},
49 vote_state::{
50 VOTE_CREDITS_MAXIMUM_PER_SLOT, VoteAuthorize, VoteInit, VoteInitV2, VoteStateV4,
51 VoterWithBLSArgs, create_bls_proof_of_possession,
52 },
53 },
54 std::rc::Rc,
55};
56
57pub trait VoteSubCommands {
58 fn vote_subcommands(self) -> Self;
59}
60
61impl VoteSubCommands for App<'_, '_> {
62 fn vote_subcommands(self) -> Self {
63 self.subcommand(
64 SubCommand::with_name("create-vote-account")
65 .about("Create a vote account")
66 .arg(
67 Arg::with_name("vote_account")
68 .index(1)
69 .value_name("ACCOUNT_KEYPAIR")
70 .takes_value(true)
71 .required(true)
72 .validator(is_valid_signer)
73 .help("Vote account keypair to create"),
74 )
75 .arg(
76 Arg::with_name("identity_account")
77 .index(2)
78 .value_name("IDENTITY_KEYPAIR")
79 .takes_value(true)
80 .required(true)
81 .validator(is_valid_signer)
82 .help("Keypair of validator that will vote with this account"),
83 )
84 .arg(pubkey!(
85 Arg::with_name("authorized_withdrawer")
86 .index(3)
87 .value_name("WITHDRAWER_PUBKEY")
88 .takes_value(true)
89 .required(true)
90 .long("authorized-withdrawer"),
91 "Authorized withdrawer."
92 ))
93 .arg(
94 Arg::with_name("commission")
95 .long("commission")
96 .value_name("PERCENTAGE")
97 .takes_value(true)
98 .help(
99 "The commission taken on reward redemption (0-100). Only valid for \
100 VoteInit (v1). Cannot be used with --use-v2-instruction. [default: \
101 100]",
102 ),
103 )
104 .arg(pubkey!(
105 Arg::with_name("authorized_voter")
106 .long("authorized-voter")
107 .value_name("VOTER_PUBKEY"),
108 "Authorized voter [default: validator identity pubkey]."
109 ))
110 .arg(
112 Arg::with_name("use_v2_instruction")
113 .long("use-v2-instruction")
114 .takes_value(false)
115 .help(
116 "Force use of VoteInitV2 (SIMD-0464). Required in sign-only mode \
117 after feature activation. In normal mode, instruction version is \
118 auto-detected based on feature status.",
119 ),
120 )
121 .arg(
122 Arg::with_name("inflation_rewards_commission_bps")
123 .long("inflation-rewards-commission-bps")
124 .value_name("BASIS_POINTS")
125 .takes_value(true)
126 .validator(is_valid_basis_points)
127 .help(
128 "Commission rate in basis points (0-10000) for inflation rewards. 100 \
129 basis points = 1%. Only valid with VoteInitV2 (--use-v2-instruction \
130 or when SIMD-0464 feature is active). [default: 10000 (100%)]",
131 ),
132 )
133 .arg(pubkey!(
134 Arg::with_name("inflation_rewards_collector")
135 .long("inflation-rewards-collector")
136 .value_name("COLLECTOR_PUBKEY")
137 .takes_value(true),
138 "Account to collect inflation rewards commission. Only valid with VoteInitV2 \
139 (--use-v2-instruction or when SIMD-0464 feature is active). [default: vote \
140 account address]"
141 ))
142 .arg(
143 Arg::with_name("block_revenue_commission_bps")
144 .long("block-revenue-commission-bps")
145 .value_name("BASIS_POINTS")
146 .takes_value(true)
147 .validator(is_valid_basis_points)
148 .help(
149 "Commission rate in basis points (0-10000) for block revenue. 100 \
150 basis points = 1%. Only valid with VoteInitV2 (--use-v2-instruction \
151 or when SIMD-0464 feature is active). [default: 10000 (100%)]",
152 ),
153 )
154 .arg(pubkey!(
155 Arg::with_name("block_revenue_collector")
156 .long("block-revenue-collector")
157 .value_name("COLLECTOR_PUBKEY")
158 .takes_value(true),
159 "Account to collect block revenue commission. Only valid with VoteInitV2 \
160 (--use-v2-instruction or when SIMD-0464 feature is active). [default: \
161 identity account address]"
162 ))
163 .arg(
164 Arg::with_name("allow_unsafe_authorized_withdrawer")
165 .long("allow-unsafe-authorized-withdrawer")
166 .takes_value(false)
167 .help(
168 "Allow an authorized withdrawer pubkey to be identical to the \
169 validator identity account pubkey or vote account pubkey, which is \
170 normally an unsafe configuration and should be avoided.",
171 ),
172 )
173 .arg(
174 Arg::with_name("seed")
175 .long("seed")
176 .value_name("STRING")
177 .takes_value(true)
178 .help(
179 "Seed for address generation; if specified, the resulting account \
180 will be at a derived address of the VOTE ACCOUNT pubkey",
181 ),
182 )
183 .offline_args()
184 .nonce_args(false)
185 .arg(fee_payer_arg())
186 .arg(memo_arg())
187 .arg(compute_unit_price_arg()),
188 )
189 .subcommand(
190 SubCommand::with_name("vote-authorize-voter")
191 .about("Authorize a new vote signing keypair for the given vote account")
192 .arg(pubkey!(
193 Arg::with_name("vote_account_pubkey")
194 .index(1)
195 .value_name("VOTE_ACCOUNT_ADDRESS")
196 .required(true),
197 "Vote account in which to set the authorized voter."
198 ))
199 .arg(
200 Arg::with_name("authorized")
201 .index(2)
202 .value_name("AUTHORIZED_KEYPAIR")
203 .required(true)
204 .validator(is_valid_signer)
205 .help("Current authorized vote signer."),
206 )
207 .arg(pubkey!(
208 Arg::with_name("new_authorized_pubkey")
209 .index(3)
210 .value_name("NEW_AUTHORIZED_PUBKEY")
211 .required(true),
212 "New authorized vote signer."
213 ))
214 .offline_args()
215 .nonce_args(false)
216 .arg(fee_payer_arg())
217 .arg(memo_arg())
218 .arg(compute_unit_price_arg()),
219 )
220 .subcommand(
221 SubCommand::with_name("vote-authorize-withdrawer")
222 .about("Authorize a new withdraw signing keypair for the given vote account")
223 .arg(pubkey!(
224 Arg::with_name("vote_account_pubkey")
225 .index(1)
226 .value_name("VOTE_ACCOUNT_ADDRESS")
227 .required(true),
228 "Vote account in which to set the authorized withdrawer."
229 ))
230 .arg(
231 Arg::with_name("authorized")
232 .index(2)
233 .value_name("AUTHORIZED_KEYPAIR")
234 .required(true)
235 .validator(is_valid_signer)
236 .help("Current authorized withdrawer."),
237 )
238 .arg(pubkey!(
239 Arg::with_name("new_authorized_pubkey")
240 .index(3)
241 .value_name("AUTHORIZED_PUBKEY")
242 .required(true),
243 "New authorized withdrawer."
244 ))
245 .offline_args()
246 .nonce_args(false)
247 .arg(fee_payer_arg())
248 .arg(memo_arg())
249 .arg(compute_unit_price_arg()),
250 )
251 .subcommand(
252 SubCommand::with_name("vote-authorize-voter-checked")
253 .about(
254 "Authorize a new vote signing keypair for the given vote account, checking \
255 the new authority as a signer",
256 )
257 .arg(pubkey!(
258 Arg::with_name("vote_account_pubkey")
259 .index(1)
260 .value_name("VOTE_ACCOUNT_ADDRESS")
261 .required(true),
262 "Vote account in which to set the authorized voter."
263 ))
264 .arg(
265 Arg::with_name("authorized")
266 .index(2)
267 .value_name("AUTHORIZED_KEYPAIR")
268 .required(true)
269 .validator(is_valid_signer)
270 .help("Current authorized vote signer."),
271 )
272 .arg(
273 Arg::with_name("new_authorized")
274 .index(3)
275 .value_name("NEW_AUTHORIZED_KEYPAIR")
276 .required(true)
277 .validator(is_valid_signer)
278 .help("New authorized vote signer."),
279 )
280 .arg(
281 Arg::with_name("use_v2_instruction")
282 .long("use-v2-instruction")
283 .takes_value(false)
284 .help(
285 "Force BLS key derivation (SIMD-0387). Required in sign-only mode \
286 after feature activation. In normal mode, BLS usage is auto-detected \
287 based on feature status.",
288 ),
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("vote-authorize-withdrawer-checked")
298 .about(
299 "Authorize a new withdraw signing keypair for the given vote account, \
300 checking the new authority as a signer",
301 )
302 .arg(pubkey!(
303 Arg::with_name("vote_account_pubkey")
304 .index(1)
305 .value_name("VOTE_ACCOUNT_ADDRESS")
306 .required(true),
307 "Vote account in which to set the authorized withdrawer."
308 ))
309 .arg(
310 Arg::with_name("authorized")
311 .index(2)
312 .value_name("AUTHORIZED_KEYPAIR")
313 .required(true)
314 .validator(is_valid_signer)
315 .help("Current authorized withdrawer."),
316 )
317 .arg(
318 Arg::with_name("new_authorized")
319 .index(3)
320 .value_name("NEW_AUTHORIZED_KEYPAIR")
321 .required(true)
322 .validator(is_valid_signer)
323 .help("New authorized withdrawer."),
324 )
325 .offline_args()
326 .nonce_args(false)
327 .arg(fee_payer_arg())
328 .arg(memo_arg())
329 .arg(compute_unit_price_arg()),
330 )
331 .subcommand(
332 SubCommand::with_name("vote-update-validator")
333 .about("Update the vote account's validator identity")
334 .arg(pubkey!(
335 Arg::with_name("vote_account_pubkey")
336 .index(1)
337 .value_name("VOTE_ACCOUNT_ADDRESS")
338 .required(true),
339 "Vote account to update."
340 ))
341 .arg(
342 Arg::with_name("new_identity_account")
343 .index(2)
344 .value_name("IDENTITY_KEYPAIR")
345 .takes_value(true)
346 .required(true)
347 .validator(is_valid_signer)
348 .help("Keypair of new validator that will vote with this account"),
349 )
350 .arg(
351 Arg::with_name("authorized_withdrawer")
352 .index(3)
353 .value_name("AUTHORIZED_KEYPAIR")
354 .takes_value(true)
355 .required(true)
356 .validator(is_valid_signer)
357 .help("Authorized withdrawer keypair"),
358 )
359 .offline_args()
360 .nonce_args(false)
361 .arg(fee_payer_arg())
362 .arg(memo_arg())
363 .arg(compute_unit_price_arg()),
364 )
365 .subcommand(
366 SubCommand::with_name("vote-update-commission")
367 .about("Update the vote account's commission")
368 .arg(pubkey!(
369 Arg::with_name("vote_account_pubkey")
370 .index(1)
371 .value_name("VOTE_ACCOUNT_ADDRESS")
372 .required(true),
373 "Vote account to update."
374 ))
375 .arg(
376 Arg::with_name("commission")
377 .index(2)
378 .value_name("PERCENTAGE")
379 .takes_value(true)
380 .required(true)
381 .validator(is_valid_percentage)
382 .help("The new commission"),
383 )
384 .arg(
385 Arg::with_name("authorized_withdrawer")
386 .index(3)
387 .value_name("AUTHORIZED_KEYPAIR")
388 .takes_value(true)
389 .required(true)
390 .validator(is_valid_signer)
391 .help("Authorized withdrawer keypair"),
392 )
393 .offline_args()
394 .nonce_args(false)
395 .arg(fee_payer_arg())
396 .arg(memo_arg())
397 .arg(compute_unit_price_arg()),
398 )
399 .subcommand(
400 SubCommand::with_name("vote-account")
401 .about("Show the contents of a vote account")
402 .alias("show-vote-account")
403 .arg(pubkey!(
404 Arg::with_name("vote_account_pubkey")
405 .index(1)
406 .value_name("VOTE_ACCOUNT_ADDRESS")
407 .required(true),
408 "Vote account."
409 ))
410 .arg(
411 Arg::with_name("lamports")
412 .long("lamports")
413 .takes_value(false)
414 .help("Display balance in lamports instead of SOL"),
415 )
416 .arg(
417 Arg::with_name("with_rewards")
418 .long("with-rewards")
419 .takes_value(false)
420 .help("Display inflation rewards"),
421 )
422 .arg(
423 Arg::with_name("csv")
424 .long("csv")
425 .takes_value(false)
426 .help("Format rewards in a CSV table"),
427 )
428 .arg(
429 Arg::with_name("starting_epoch")
430 .long("starting-epoch")
431 .takes_value(true)
432 .value_name("NUM")
433 .requires("with_rewards")
434 .help("Start displaying from epoch NUM"),
435 )
436 .arg(
437 Arg::with_name("num_rewards_epochs")
438 .long("num-rewards-epochs")
439 .takes_value(true)
440 .value_name("NUM")
441 .validator(|s| is_within_range(s, 1..=50))
442 .default_value_if("with_rewards", None, "1")
443 .requires("with_rewards")
444 .help(
445 "Display rewards for NUM recent epochs, max 10 [default: latest epoch \
446 only]",
447 ),
448 ),
449 )
450 .subcommand(
451 SubCommand::with_name("withdraw-from-vote-account")
452 .about("Withdraw lamports from a vote account into a specified account")
453 .arg(pubkey!(
454 Arg::with_name("vote_account_pubkey")
455 .index(1)
456 .value_name("VOTE_ACCOUNT_ADDRESS")
457 .required(true),
458 "Vote account from which to withdraw."
459 ))
460 .arg(pubkey!(
461 Arg::with_name("destination_account_pubkey")
462 .index(2)
463 .value_name("RECIPIENT_ADDRESS")
464 .required(true),
465 "The recipient of withdrawn SOL."
466 ))
467 .arg(
468 Arg::with_name("amount")
469 .index(3)
470 .value_name("AMOUNT")
471 .takes_value(true)
472 .required(true)
473 .validator(is_amount_or_all)
474 .help(
475 "The amount to withdraw, in SOL; accepts keyword ALL, which for this \
476 command means account balance minus rent-exempt minimum",
477 ),
478 )
479 .arg(
480 Arg::with_name("authorized_withdrawer")
481 .long("authorized-withdrawer")
482 .value_name("AUTHORIZED_KEYPAIR")
483 .takes_value(true)
484 .validator(is_valid_signer)
485 .help("Authorized withdrawer [default: cli config keypair]"),
486 )
487 .offline_args()
488 .nonce_args(false)
489 .arg(fee_payer_arg())
490 .arg(memo_arg())
491 .arg(compute_unit_price_arg()),
492 )
493 .subcommand(
494 SubCommand::with_name("close-vote-account")
495 .about("Close a vote account and withdraw all funds remaining")
496 .arg(pubkey!(
497 Arg::with_name("vote_account_pubkey")
498 .index(1)
499 .value_name("VOTE_ACCOUNT_ADDRESS")
500 .required(true),
501 "Vote account to be closed."
502 ))
503 .arg(pubkey!(
504 Arg::with_name("destination_account_pubkey")
505 .index(2)
506 .value_name("RECIPIENT_ADDRESS")
507 .required(true),
508 "The recipient of all withdrawn SOL."
509 ))
510 .arg(
511 Arg::with_name("authorized_withdrawer")
512 .long("authorized-withdrawer")
513 .value_name("AUTHORIZED_KEYPAIR")
514 .takes_value(true)
515 .validator(is_valid_signer)
516 .help("Authorized withdrawer [default: cli config keypair]"),
517 )
518 .arg(fee_payer_arg())
519 .arg(memo_arg())
520 .arg(compute_unit_price_arg()),
521 )
522 }
523}
524
525pub fn parse_create_vote_account(
526 matches: &ArgMatches<'_>,
527 default_signer: &DefaultSigner,
528 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
529) -> Result<CliCommandInfo, CliError> {
530 let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
531 let seed = matches.value_of("seed").map(|s| s.to_string());
532 let (identity_account, identity_pubkey) =
533 signer_of(matches, "identity_account", wallet_manager)?;
534 let authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
535 let authorized_withdrawer =
536 pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap();
537 let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer");
538 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
539 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
540 let blockhash_query = BlockhashQuery::new_from_matches(matches);
541 let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
542 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
543 let (nonce_authority, nonce_authority_pubkey) =
544 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
545 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
546 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
547
548 let commission: Option<u8> = value_of(matches, "commission");
550
551 let use_v2_instruction = matches.is_present("use_v2_instruction");
553 let inflation_rewards_commission_bps: Option<u16> =
554 value_of(matches, "inflation_rewards_commission_bps");
555 let inflation_rewards_collector =
556 pubkey_of_signer(matches, "inflation_rewards_collector", wallet_manager)?;
557 let block_revenue_commission_bps: Option<u16> =
558 value_of(matches, "block_revenue_commission_bps");
559 let block_revenue_collector =
560 pubkey_of_signer(matches, "block_revenue_collector", wallet_manager)?;
561
562 let has_v2_args = use_v2_instruction
566 || inflation_rewards_commission_bps.is_some()
567 || inflation_rewards_collector.is_some()
568 || block_revenue_commission_bps.is_some()
569 || block_revenue_collector.is_some();
570
571 if commission.is_some() && has_v2_args {
572 return Err(CliError::BadParameter(
573 "--commission cannot be used with --use-v2-instruction or VoteInitV2 arguments \
574 (--inflation-rewards-commission-bps, --inflation-rewards-collector, \
575 --block-revenue-commission-bps, --block-revenue-collector). For VoteInitV2, use \
576 --inflation-rewards-commission-bps instead."
577 .to_owned(),
578 ));
579 }
580
581 if !allow_unsafe {
582 if authorized_withdrawer == vote_account_pubkey.unwrap() {
583 return Err(CliError::BadParameter(
584 "Authorized withdrawer pubkey is identical to vote account pubkey, an unsafe \
585 configuration"
586 .to_owned(),
587 ));
588 }
589 if authorized_withdrawer == identity_pubkey.unwrap() {
590 return Err(CliError::BadParameter(
591 "Authorized withdrawer pubkey is identical to identity account pubkey, an unsafe \
592 configuration"
593 .to_owned(),
594 ));
595 }
596 }
597
598 let mut bulk_signers = vec![fee_payer, vote_account, identity_account];
599 if nonce_account.is_some() {
600 bulk_signers.push(nonce_authority);
601 }
602 let signer_info =
603 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
604
605 Ok(CliCommandInfo {
606 command: CliCommand::CreateVoteAccount {
607 vote_account: signer_info.index_of(vote_account_pubkey).unwrap(),
608 seed,
609 identity_account: signer_info.index_of(identity_pubkey).unwrap(),
610 authorized_voter,
611 authorized_withdrawer,
612 commission,
613 use_v2_instruction,
614 inflation_rewards_commission_bps,
615 inflation_rewards_collector,
616 block_revenue_commission_bps,
617 block_revenue_collector,
618 sign_only,
619 dump_transaction_message,
620 blockhash_query,
621 nonce_account,
622 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
623 memo,
624 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
625 compute_unit_price,
626 },
627 signers: signer_info.signers,
628 })
629}
630
631pub fn parse_vote_authorize(
632 matches: &ArgMatches<'_>,
633 default_signer: &DefaultSigner,
634 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
635 vote_authorize: VoteAuthorize,
636 checked: bool,
637) -> Result<CliCommandInfo, CliError> {
638 let vote_account_pubkey =
639 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
640 let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?;
641
642 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
643 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
644 let blockhash_query = BlockhashQuery::new_from_matches(matches);
645 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
646 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
647 let (nonce_authority, nonce_authority_pubkey) =
648 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
649 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
650 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
651
652 let use_v2_instruction = matches.is_present("use_v2_instruction");
653 if use_v2_instruction && vote_authorize != VoteAuthorize::Voter {
654 return Err(CliError::BadParameter(
655 "--use-v2-instruction is only supported for voter authorization".to_owned(),
656 ));
657 }
658
659 let mut bulk_signers = vec![fee_payer, authorized];
660
661 let new_authorized_pubkey = if checked {
662 let (new_authorized_signer, new_authorized_pubkey) =
663 signer_of(matches, "new_authorized", wallet_manager)?;
664 bulk_signers.push(new_authorized_signer);
665 new_authorized_pubkey.unwrap()
666 } else {
667 pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap()
668 };
669 if nonce_account.is_some() {
670 bulk_signers.push(nonce_authority);
671 }
672 let signer_info =
673 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
674
675 Ok(CliCommandInfo {
676 command: CliCommand::VoteAuthorize {
677 vote_account_pubkey,
678 new_authorized_pubkey,
679 vote_authorize,
680 use_v2_instruction,
681 sign_only,
682 dump_transaction_message,
683 blockhash_query,
684 nonce_account,
685 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
686 memo,
687 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
688 authorized: signer_info.index_of(authorized_pubkey).unwrap(),
689 new_authorized: if checked {
690 signer_info.index_of(Some(new_authorized_pubkey))
691 } else {
692 None
693 },
694 compute_unit_price,
695 },
696 signers: signer_info.signers,
697 })
698}
699
700pub fn parse_vote_update_validator(
701 matches: &ArgMatches<'_>,
702 default_signer: &DefaultSigner,
703 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
704) -> Result<CliCommandInfo, CliError> {
705 let vote_account_pubkey =
706 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
707 let (new_identity_account, new_identity_pubkey) =
708 signer_of(matches, "new_identity_account", wallet_manager)?;
709 let (authorized_withdrawer, authorized_withdrawer_pubkey) =
710 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
711
712 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
713 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
714 let blockhash_query = BlockhashQuery::new_from_matches(matches);
715 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
716 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
717 let (nonce_authority, nonce_authority_pubkey) =
718 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
719 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
720 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
721
722 let mut bulk_signers = vec![fee_payer, authorized_withdrawer, new_identity_account];
723 if nonce_account.is_some() {
724 bulk_signers.push(nonce_authority);
725 }
726 let signer_info =
727 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
728
729 Ok(CliCommandInfo {
730 command: CliCommand::VoteUpdateValidator {
731 vote_account_pubkey,
732 new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
733 withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
734 sign_only,
735 dump_transaction_message,
736 blockhash_query,
737 nonce_account,
738 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
739 memo,
740 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
741 compute_unit_price,
742 },
743 signers: signer_info.signers,
744 })
745}
746
747pub fn parse_vote_update_commission(
748 matches: &ArgMatches<'_>,
749 default_signer: &DefaultSigner,
750 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
751) -> Result<CliCommandInfo, CliError> {
752 let vote_account_pubkey =
753 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
754 let (authorized_withdrawer, authorized_withdrawer_pubkey) =
755 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
756 let commission = value_t_or_exit!(matches, "commission", u8);
757
758 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
759 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
760 let blockhash_query = BlockhashQuery::new_from_matches(matches);
761 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
762 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
763 let (nonce_authority, nonce_authority_pubkey) =
764 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
765 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
766 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
767
768 let mut bulk_signers = vec![fee_payer, authorized_withdrawer];
769 if nonce_account.is_some() {
770 bulk_signers.push(nonce_authority);
771 }
772 let signer_info =
773 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
774
775 Ok(CliCommandInfo {
776 command: CliCommand::VoteUpdateCommission {
777 vote_account_pubkey,
778 commission,
779 withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
780 sign_only,
781 dump_transaction_message,
782 blockhash_query,
783 nonce_account,
784 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
785 memo,
786 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
787 compute_unit_price,
788 },
789 signers: signer_info.signers,
790 })
791}
792
793pub fn parse_vote_get_account_command(
794 matches: &ArgMatches<'_>,
795 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
796) -> Result<CliCommandInfo, CliError> {
797 let vote_account_pubkey =
798 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
799 let use_lamports_unit = matches.is_present("lamports");
800 let use_csv = matches.is_present("csv");
801 let with_rewards = if matches.is_present("with_rewards") {
802 Some(value_of(matches, "num_rewards_epochs").unwrap())
803 } else {
804 None
805 };
806 let starting_epoch = value_of(matches, "starting_epoch");
807 Ok(CliCommandInfo::without_signers(
808 CliCommand::ShowVoteAccount {
809 pubkey: vote_account_pubkey,
810 use_lamports_unit,
811 use_csv,
812 with_rewards,
813 starting_epoch,
814 },
815 ))
816}
817
818pub fn parse_withdraw_from_vote_account(
819 matches: &ArgMatches<'_>,
820 default_signer: &DefaultSigner,
821 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
822) -> Result<CliCommandInfo, CliError> {
823 let vote_account_pubkey =
824 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
825 let destination_account_pubkey =
826 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
827 let mut withdraw_amount = SpendAmount::new_from_matches(matches, "amount");
828 if withdraw_amount == SpendAmount::All {
832 withdraw_amount = SpendAmount::RentExempt;
833 }
834
835 let (withdraw_authority, withdraw_authority_pubkey) =
836 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
837
838 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
839 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
840 let blockhash_query = BlockhashQuery::new_from_matches(matches);
841 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
842 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
843 let (nonce_authority, nonce_authority_pubkey) =
844 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
845 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
846 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
847
848 let mut bulk_signers = vec![fee_payer, withdraw_authority];
849 if nonce_account.is_some() {
850 bulk_signers.push(nonce_authority);
851 }
852 let signer_info =
853 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
854
855 Ok(CliCommandInfo {
856 command: CliCommand::WithdrawFromVoteAccount {
857 vote_account_pubkey,
858 destination_account_pubkey,
859 withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
860 withdraw_amount,
861 sign_only,
862 dump_transaction_message,
863 blockhash_query,
864 nonce_account,
865 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
866 memo,
867 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
868 compute_unit_price,
869 },
870 signers: signer_info.signers,
871 })
872}
873
874pub fn parse_close_vote_account(
875 matches: &ArgMatches<'_>,
876 default_signer: &DefaultSigner,
877 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
878) -> Result<CliCommandInfo, CliError> {
879 let vote_account_pubkey =
880 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
881 let destination_account_pubkey =
882 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
883
884 let (withdraw_authority, withdraw_authority_pubkey) =
885 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
886 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
887
888 let signer_info = default_signer.generate_unique_signers(
889 vec![fee_payer, withdraw_authority],
890 matches,
891 wallet_manager,
892 )?;
893 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
894 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
895
896 Ok(CliCommandInfo {
897 command: CliCommand::CloseVoteAccount {
898 vote_account_pubkey,
899 destination_account_pubkey,
900 withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
901 memo,
902 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
903 compute_unit_price,
904 },
905 signers: signer_info.signers,
906 })
907}
908
909#[allow(clippy::too_many_arguments)]
910pub async fn process_create_vote_account(
911 rpc_client: &RpcClient,
912 config: &CliConfig<'_>,
913 vote_account: SignerIndex,
914 seed: &Option<String>,
915 identity_account: SignerIndex,
916 authorized_voter: &Option<Pubkey>,
917 authorized_withdrawer: Pubkey,
918 commission: Option<u8>,
920 use_v2_instruction: bool,
922 inflation_rewards_commission_bps: Option<u16>,
923 inflation_rewards_collector: Option<&Pubkey>,
924 block_revenue_commission_bps: Option<u16>,
925 block_revenue_collector: Option<&Pubkey>,
926 sign_only: bool,
928 dump_transaction_message: bool,
929 blockhash_query: &BlockhashQuery,
930 nonce_account: Option<&Pubkey>,
931 nonce_authority: SignerIndex,
932 memo: Option<&String>,
933 fee_payer: SignerIndex,
934 compute_unit_price: Option<u64>,
935) -> ProcessResult {
936 let vote_account = config.signers[vote_account];
937 let vote_account_pubkey = vote_account.pubkey();
938 let vote_account_address = if let Some(seed) = seed {
939 Pubkey::create_with_seed(&vote_account_pubkey, seed, &solana_vote_program::id())?
940 } else {
941 vote_account_pubkey
942 };
943 check_unique_pubkeys(
944 (&config.signers[0].pubkey(), "cli keypair".to_string()),
945 (&vote_account_address, "vote_account".to_string()),
946 )?;
947
948 let identity_account = config.signers[identity_account];
949 let identity_pubkey = identity_account.pubkey();
950 check_unique_pubkeys(
951 (&vote_account_address, "vote_account".to_string()),
952 (&identity_pubkey, "identity_pubkey".to_string()),
953 )?;
954
955 let use_v2 = if use_v2_instruction {
961 true
963 } else if sign_only {
964 false
966 } else {
967 get_feature_is_active(rpc_client, &vote_account_initialize_v2::id())
969 .await
970 .unwrap_or(false)
971 };
972
973 let has_v2_args = inflation_rewards_commission_bps.is_some()
976 || inflation_rewards_collector.is_some()
977 || block_revenue_commission_bps.is_some()
978 || block_revenue_collector.is_some();
979
980 if !use_v2 && has_v2_args {
981 return Err(CliError::BadParameter(
982 "VoteInitV2 arguments (--inflation-rewards-commission-bps, \
983 --inflation-rewards-collector, --block-revenue-commission-bps, \
984 --block-revenue-collector) require --use-v2-instruction flag or SIMD-0464 feature to \
985 be active."
986 .to_owned(),
987 )
988 .into());
989 }
990
991 let required_balance = rpc_client
992 .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
993 .await?
994 .max(1);
995 let amount = SpendAmount::Some(required_balance);
996
997 let fee_payer = config.signers[fee_payer];
998 let nonce_authority = config.signers[nonce_authority];
999 let space = VoteStateV4::size_of() as u64;
1000
1001 let compute_unit_limit = match blockhash_query {
1002 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1003 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1004 };
1005
1006 let bls_data = if use_v2 {
1009 let derived_bls_keypair =
1010 BLSKeypair::derive_from_signer(identity_account, BLS_KEYPAIR_DERIVE_SEED).map_err(
1011 |e| CliError::BadParameter(format!("Failed to derive BLS keypair: {e}")),
1012 )?;
1013 let (bls_pubkey, bls_proof_of_possession) =
1014 create_bls_proof_of_possession(&vote_account_address, &derived_bls_keypair);
1015 Some((bls_pubkey, bls_proof_of_possession))
1016 } else {
1017 None
1018 };
1019
1020 let build_message = |lamports| {
1021 let mut create_vote_account_config = CreateVoteAccountConfig {
1022 space,
1023 ..CreateVoteAccountConfig::default()
1024 };
1025 let to = if let Some(seed) = seed {
1026 create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed));
1027 &vote_account_address
1028 } else {
1029 &vote_account_pubkey
1030 };
1031
1032 let ixs = if use_v2 {
1033 let (bls_pubkey, bls_proof_of_possession) = bls_data.unwrap();
1034 let vote_init = VoteInitV2 {
1035 node_pubkey: identity_pubkey,
1036 authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
1037 authorized_voter_bls_pubkey: bls_pubkey,
1038 authorized_voter_bls_proof_of_possession: bls_proof_of_possession,
1039 authorized_withdrawer,
1040 inflation_rewards_commission_bps: inflation_rewards_commission_bps
1041 .or_else(|| commission.map(|c| (c as u16).saturating_mul(100))) .unwrap_or(10000),
1043 inflation_rewards_collector: inflation_rewards_collector
1044 .copied()
1045 .unwrap_or(vote_account_address),
1046 block_revenue_commission_bps: block_revenue_commission_bps.unwrap_or(10000),
1047 block_revenue_collector: block_revenue_collector
1048 .copied()
1049 .unwrap_or(identity_pubkey),
1050 };
1051 vote_instruction::create_account_with_config_v2(
1052 &config.signers[0].pubkey(),
1053 to,
1054 &vote_init,
1055 lamports,
1056 create_vote_account_config,
1057 )
1058 } else {
1059 let vote_init = VoteInit {
1060 node_pubkey: identity_pubkey,
1061 authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
1062 authorized_withdrawer,
1063 commission: commission.unwrap_or(100),
1064 };
1065 vote_instruction::create_account_with_config(
1066 &config.signers[0].pubkey(),
1067 to,
1068 &vote_init,
1069 lamports,
1070 create_vote_account_config,
1071 )
1072 };
1073
1074 let ixs = ixs
1075 .with_memo(memo)
1076 .with_compute_unit_config(&ComputeUnitConfig {
1077 compute_unit_price,
1078 compute_unit_limit,
1079 });
1080
1081 if let Some(nonce_account) = &nonce_account {
1082 Message::new_with_nonce(
1083 ixs,
1084 Some(&fee_payer.pubkey()),
1085 nonce_account,
1086 &nonce_authority.pubkey(),
1087 )
1088 } else {
1089 Message::new(&ixs, Some(&fee_payer.pubkey()))
1090 }
1091 };
1092
1093 let recent_blockhash = blockhash_query
1094 .get_blockhash(rpc_client, config.commitment)
1095 .await?;
1096
1097 let (message, _) = resolve_spend_tx_and_check_account_balances(
1098 rpc_client,
1099 sign_only,
1100 amount,
1101 &recent_blockhash,
1102 &config.signers[0].pubkey(),
1103 &fee_payer.pubkey(),
1104 compute_unit_limit,
1105 build_message,
1106 config.commitment,
1107 )
1108 .await?;
1109
1110 if !sign_only {
1111 if let Ok(response) = rpc_client
1112 .get_account_with_commitment(&vote_account_address, config.commitment)
1113 .await
1114 {
1115 if let Some(vote_account) = response.value {
1116 let err_msg = if vote_account.owner == solana_vote_program::id() {
1117 format!("Vote account {vote_account_address} already exists")
1118 } else {
1119 format!(
1120 "Account {vote_account_address} already exists and is not a vote account"
1121 )
1122 };
1123 return Err(CliError::BadParameter(err_msg).into());
1124 }
1125 }
1126
1127 if let Some(nonce_account) = &nonce_account {
1128 let nonce_account =
1129 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1130 rpc_client,
1131 nonce_account,
1132 config.commitment,
1133 )
1134 .await?;
1135 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1136 }
1137 }
1138
1139 let mut tx = Transaction::new_unsigned(message);
1140 if sign_only {
1141 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1142 return_signers_with_config(
1143 &tx,
1144 &config.output_format,
1145 &ReturnSignersConfig {
1146 dump_transaction_message,
1147 },
1148 )
1149 } else {
1150 tx.try_sign(&config.signers, recent_blockhash)?;
1151 let result = rpc_client
1152 .send_and_confirm_transaction_with_spinner_and_config(
1153 &tx,
1154 config.commitment,
1155 config.send_transaction_config,
1156 )
1157 .await;
1158 log_instruction_custom_error::<SystemError>(result, config)
1159 }
1160}
1161
1162#[allow(clippy::too_many_arguments)]
1163pub async fn process_vote_authorize(
1164 rpc_client: &RpcClient,
1165 config: &CliConfig<'_>,
1166 vote_account_pubkey: &Pubkey,
1167 new_authorized_pubkey: &Pubkey,
1168 vote_authorize: VoteAuthorize,
1169 use_v2_instruction: bool,
1170 authorized: SignerIndex,
1171 new_authorized: Option<SignerIndex>,
1172 sign_only: bool,
1173 dump_transaction_message: bool,
1174 blockhash_query: &BlockhashQuery,
1175 nonce_account: Option<Pubkey>,
1176 nonce_authority: SignerIndex,
1177 memo: Option<&String>,
1178 fee_payer: SignerIndex,
1179 compute_unit_price: Option<u64>,
1180) -> ProcessResult {
1181 let authorized = config.signers[authorized];
1182 let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
1183 let is_checked = new_authorized_signer.is_some();
1184
1185 let vote_state = if !sign_only {
1186 Some(
1187 get_vote_account(rpc_client, vote_account_pubkey, config.commitment)
1188 .await?
1189 .1,
1190 )
1191 } else {
1192 None
1193 };
1194
1195 let use_bls = if !matches!(vote_authorize, VoteAuthorize::Voter) {
1203 false
1205 } else if use_v2_instruction {
1206 true
1208 } else if sign_only {
1209 false
1211 } else if vote_state
1212 .as_ref()
1213 .map(|vs| vs.bls_pubkey_compressed.is_some())
1214 .unwrap_or(false)
1215 {
1216 true
1218 } else {
1219 get_feature_is_active(rpc_client, &bls_pubkey_management_in_vote_account::id())
1221 .await
1222 .unwrap_or(false)
1223 };
1224
1225 match vote_authorize {
1226 VoteAuthorize::Voter => {
1227 if let Some(vote_state) = vote_state {
1228 let current_epoch = rpc_client.get_epoch_info().await?.epoch;
1229 let current_authorized_voter = vote_state
1230 .authorized_voters
1231 .get_authorized_voter(current_epoch)
1232 .ok_or_else(|| {
1233 CliError::RpcRequestError(
1234 "Invalid vote account state; no authorized voters found".to_string(),
1235 )
1236 })?;
1237 check_current_authority(
1238 &[current_authorized_voter, vote_state.authorized_withdrawer],
1239 &authorized.pubkey(),
1240 )?;
1241 if let Some(signer) = new_authorized_signer {
1242 if signer.is_interactive() {
1243 return Err(CliError::BadParameter(format!(
1244 "invalid new authorized vote signer {new_authorized_pubkey:?}. \
1245 Interactive vote signers not supported"
1246 ))
1247 .into());
1248 }
1249 }
1250 }
1251 }
1252 VoteAuthorize::Withdrawer => {
1253 check_unique_pubkeys(
1254 (&authorized.pubkey(), "authorized_account".to_string()),
1255 (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
1256 )?;
1257 if let Some(vote_state) = vote_state {
1258 check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())?
1259 }
1260 }
1261 VoteAuthorize::VoterWithBLS(_) => {
1262 unreachable!("VoterWithBLS should not be passed as vote_authorize parameter");
1265 }
1266 }
1267
1268 let effective_vote_authorize = if use_bls {
1271 if !is_checked {
1272 return Err(CliError::BadParameter(
1273 "BLS key derivation requires the new voter to be a signer. Use \
1274 `vote-authorize-voter-checked` instead."
1275 .to_owned(),
1276 )
1277 .into());
1278 }
1279 let new_authorized_signer = new_authorized_signer.unwrap();
1280 let derived_bls_keypair =
1281 BLSKeypair::derive_from_signer(new_authorized_signer, BLS_KEYPAIR_DERIVE_SEED)
1282 .map_err(|e| {
1283 CliError::BadParameter(format!("Failed to derive BLS keypair: {e}"))
1284 })?;
1285 let (bls_pubkey, bls_proof_of_possession) =
1286 create_bls_proof_of_possession(vote_account_pubkey, &derived_bls_keypair);
1287 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
1288 bls_pubkey,
1289 bls_proof_of_possession,
1290 })
1291 } else {
1292 vote_authorize
1293 };
1294
1295 let vote_ix = if is_checked {
1296 vote_instruction::authorize_checked(
1297 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, effective_vote_authorize, )
1302 } else {
1303 vote_instruction::authorize(
1304 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, effective_vote_authorize, )
1309 };
1310
1311 let compute_unit_limit = match blockhash_query {
1312 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1313 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1314 };
1315 let ixs = vec![vote_ix]
1316 .with_memo(memo)
1317 .with_compute_unit_config(&ComputeUnitConfig {
1318 compute_unit_price,
1319 compute_unit_limit,
1320 });
1321
1322 let recent_blockhash = blockhash_query
1323 .get_blockhash(rpc_client, config.commitment)
1324 .await?;
1325
1326 let nonce_authority = config.signers[nonce_authority];
1327 let fee_payer = config.signers[fee_payer];
1328
1329 let mut message = if let Some(nonce_account) = &nonce_account {
1330 Message::new_with_nonce(
1331 ixs,
1332 Some(&fee_payer.pubkey()),
1333 nonce_account,
1334 &nonce_authority.pubkey(),
1335 )
1336 } else {
1337 Message::new(&ixs, Some(&fee_payer.pubkey()))
1338 };
1339 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1340 let mut tx = Transaction::new_unsigned(message);
1341
1342 if sign_only {
1343 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1344 return_signers_with_config(
1345 &tx,
1346 &config.output_format,
1347 &ReturnSignersConfig {
1348 dump_transaction_message,
1349 },
1350 )
1351 } else {
1352 tx.try_sign(&config.signers, recent_blockhash)?;
1353 if let Some(nonce_account) = &nonce_account {
1354 let nonce_account =
1355 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1356 rpc_client,
1357 nonce_account,
1358 config.commitment,
1359 )
1360 .await?;
1361 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1362 }
1363 check_account_for_fee_with_commitment(
1364 rpc_client,
1365 &config.signers[0].pubkey(),
1366 &tx.message,
1367 config.commitment,
1368 )
1369 .await?;
1370 let result = rpc_client
1371 .send_and_confirm_transaction_with_spinner_and_config(
1372 &tx,
1373 config.commitment,
1374 config.send_transaction_config,
1375 )
1376 .await;
1377 log_instruction_custom_error::<VoteError>(result, config)
1378 }
1379}
1380
1381#[allow(clippy::too_many_arguments)]
1382pub async fn process_vote_update_validator(
1383 rpc_client: &RpcClient,
1384 config: &CliConfig<'_>,
1385 vote_account_pubkey: &Pubkey,
1386 new_identity_account: SignerIndex,
1387 withdraw_authority: SignerIndex,
1388 sign_only: bool,
1389 dump_transaction_message: bool,
1390 blockhash_query: &BlockhashQuery,
1391 nonce_account: Option<Pubkey>,
1392 nonce_authority: SignerIndex,
1393 memo: Option<&String>,
1394 fee_payer: SignerIndex,
1395 compute_unit_price: Option<u64>,
1396) -> ProcessResult {
1397 let authorized_withdrawer = config.signers[withdraw_authority];
1398 let new_identity_account = config.signers[new_identity_account];
1399 let new_identity_pubkey = new_identity_account.pubkey();
1400 check_unique_pubkeys(
1401 (vote_account_pubkey, "vote_account_pubkey".to_string()),
1402 (&new_identity_pubkey, "new_identity_account".to_string()),
1403 )?;
1404 let recent_blockhash = blockhash_query
1405 .get_blockhash(rpc_client, config.commitment)
1406 .await?;
1407 let compute_unit_limit = match blockhash_query {
1408 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1409 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1410 };
1411 let ixs = vec![vote_instruction::update_validator_identity(
1412 vote_account_pubkey,
1413 &authorized_withdrawer.pubkey(),
1414 &new_identity_pubkey,
1415 )]
1416 .with_memo(memo)
1417 .with_compute_unit_config(&ComputeUnitConfig {
1418 compute_unit_price,
1419 compute_unit_limit,
1420 });
1421 let nonce_authority = config.signers[nonce_authority];
1422 let fee_payer = config.signers[fee_payer];
1423
1424 let mut message = if let Some(nonce_account) = &nonce_account {
1425 Message::new_with_nonce(
1426 ixs,
1427 Some(&fee_payer.pubkey()),
1428 nonce_account,
1429 &nonce_authority.pubkey(),
1430 )
1431 } else {
1432 Message::new(&ixs, Some(&fee_payer.pubkey()))
1433 };
1434 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1435 let mut tx = Transaction::new_unsigned(message);
1436
1437 if sign_only {
1438 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1439 return_signers_with_config(
1440 &tx,
1441 &config.output_format,
1442 &ReturnSignersConfig {
1443 dump_transaction_message,
1444 },
1445 )
1446 } else {
1447 tx.try_sign(&config.signers, recent_blockhash)?;
1448 if let Some(nonce_account) = &nonce_account {
1449 let nonce_account =
1450 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1451 rpc_client,
1452 nonce_account,
1453 config.commitment,
1454 )
1455 .await?;
1456 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1457 }
1458 check_account_for_fee_with_commitment(
1459 rpc_client,
1460 &config.signers[0].pubkey(),
1461 &tx.message,
1462 config.commitment,
1463 )
1464 .await?;
1465 let result = rpc_client
1466 .send_and_confirm_transaction_with_spinner_and_config(
1467 &tx,
1468 config.commitment,
1469 config.send_transaction_config,
1470 )
1471 .await;
1472 log_instruction_custom_error::<VoteError>(result, config)
1473 }
1474}
1475
1476#[allow(clippy::too_many_arguments)]
1477pub async fn process_vote_update_commission(
1478 rpc_client: &RpcClient,
1479 config: &CliConfig<'_>,
1480 vote_account_pubkey: &Pubkey,
1481 commission: u8,
1482 withdraw_authority: SignerIndex,
1483 sign_only: bool,
1484 dump_transaction_message: bool,
1485 blockhash_query: &BlockhashQuery,
1486 nonce_account: Option<Pubkey>,
1487 nonce_authority: SignerIndex,
1488 memo: Option<&String>,
1489 fee_payer: SignerIndex,
1490 compute_unit_price: Option<u64>,
1491) -> ProcessResult {
1492 let authorized_withdrawer = config.signers[withdraw_authority];
1493 let recent_blockhash = blockhash_query
1494 .get_blockhash(rpc_client, config.commitment)
1495 .await?;
1496 let compute_unit_limit = match blockhash_query {
1497 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1498 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1499 };
1500 let ixs = vec![vote_instruction::update_commission(
1501 vote_account_pubkey,
1502 &authorized_withdrawer.pubkey(),
1503 commission,
1504 )]
1505 .with_memo(memo)
1506 .with_compute_unit_config(&ComputeUnitConfig {
1507 compute_unit_price,
1508 compute_unit_limit,
1509 });
1510 let nonce_authority = config.signers[nonce_authority];
1511 let fee_payer = config.signers[fee_payer];
1512
1513 let mut message = if let Some(nonce_account) = &nonce_account {
1514 Message::new_with_nonce(
1515 ixs,
1516 Some(&fee_payer.pubkey()),
1517 nonce_account,
1518 &nonce_authority.pubkey(),
1519 )
1520 } else {
1521 Message::new(&ixs, Some(&fee_payer.pubkey()))
1522 };
1523 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1524 let mut tx = Transaction::new_unsigned(message);
1525 if sign_only {
1526 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1527 return_signers_with_config(
1528 &tx,
1529 &config.output_format,
1530 &ReturnSignersConfig {
1531 dump_transaction_message,
1532 },
1533 )
1534 } else {
1535 tx.try_sign(&config.signers, recent_blockhash)?;
1536 if let Some(nonce_account) = &nonce_account {
1537 let nonce_account =
1538 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1539 rpc_client,
1540 nonce_account,
1541 config.commitment,
1542 )
1543 .await?;
1544 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1545 }
1546 check_account_for_fee_with_commitment(
1547 rpc_client,
1548 &config.signers[0].pubkey(),
1549 &tx.message,
1550 config.commitment,
1551 )
1552 .await?;
1553 let result = rpc_client
1554 .send_and_confirm_transaction_with_spinner_and_config(
1555 &tx,
1556 config.commitment,
1557 config.send_transaction_config,
1558 )
1559 .await;
1560 log_instruction_custom_error::<VoteError>(result, config)
1561 }
1562}
1563
1564pub(crate) async fn get_vote_account(
1565 rpc_client: &RpcClient,
1566 vote_account_pubkey: &Pubkey,
1567 commitment_config: CommitmentConfig,
1568) -> Result<(Account, VoteStateV4), Box<dyn std::error::Error>> {
1569 let vote_account = rpc_client
1570 .get_account_with_commitment(vote_account_pubkey, commitment_config)
1571 .await?
1572 .value
1573 .ok_or_else(|| {
1574 CliError::RpcRequestError(format!("{vote_account_pubkey:?} account does not exist"))
1575 })?;
1576
1577 if vote_account.owner != solana_vote_program::id() {
1578 return Err(CliError::RpcRequestError(format!(
1579 "{vote_account_pubkey:?} is not a vote account"
1580 ))
1581 .into());
1582 }
1583 let vote_state =
1584 VoteStateV4::deserialize(&vote_account.data, vote_account_pubkey).map_err(|_| {
1585 CliError::RpcRequestError(
1586 "Account data could not be deserialized to vote state".to_string(),
1587 )
1588 })?;
1589
1590 Ok((vote_account, vote_state))
1591}
1592
1593pub async fn process_show_vote_account(
1594 rpc_client: &RpcClient,
1595 config: &CliConfig<'_>,
1596 vote_account_address: &Pubkey,
1597 use_lamports_unit: bool,
1598 use_csv: bool,
1599 with_rewards: Option<usize>,
1600 starting_epoch: Option<u64>,
1601) -> ProcessResult {
1602 let (vote_account, vote_state) =
1603 get_vote_account(rpc_client, vote_account_address, config.commitment).await?;
1604
1605 let epoch_schedule = rpc_client.get_epoch_schedule().await?;
1606 let tvc_activation_slot = rpc_client
1607 .get_account_with_commitment(
1608 &agave_feature_set::timely_vote_credits::id(),
1609 config.commitment,
1610 )
1611 .await
1612 .ok()
1613 .and_then(|response| response.value)
1614 .and_then(|account| from_account(&account))
1615 .and_then(|feature| feature.activated_at);
1616 let tvc_activation_epoch = tvc_activation_slot.map(|s| epoch_schedule.get_epoch(s));
1617
1618 let mut votes: Vec<CliLandedVote> = vec![];
1619 let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
1620 if !vote_state.votes.is_empty() {
1621 for vote in &vote_state.votes {
1622 votes.push(vote.into());
1623 }
1624 for (epoch, credits, prev_credits) in vote_state.epoch_credits.iter().copied() {
1625 let credits_earned = credits.saturating_sub(prev_credits);
1626 let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
1627 let is_tvc_active = tvc_activation_epoch.map(|e| epoch >= e).unwrap_or_default();
1628 let max_credits_per_slot = if is_tvc_active {
1629 VOTE_CREDITS_MAXIMUM_PER_SLOT
1630 } else {
1631 1
1632 };
1633 epoch_voting_history.push(CliEpochVotingHistory {
1634 epoch,
1635 slots_in_epoch,
1636 credits_earned,
1637 credits,
1638 prev_credits,
1639 max_credits_per_slot,
1640 });
1641 }
1642 }
1643
1644 let epoch_rewards = if let Some(num_epochs) = with_rewards {
1645 match crate::stake::fetch_epoch_rewards(
1646 rpc_client,
1647 vote_account_address,
1648 num_epochs,
1649 starting_epoch,
1650 )
1651 .await
1652 {
1653 Ok(rewards) => Some(rewards),
1654 Err(error) => {
1655 eprintln!("Failed to fetch epoch rewards: {error:?}");
1656 None
1657 }
1658 }
1659 } else {
1660 None
1661 };
1662
1663 let vote_account_data = CliVoteAccount {
1664 account_balance: vote_account.lamports,
1665 validator_identity: vote_state.node_pubkey.to_string(),
1666 authorized_voters: (&vote_state.authorized_voters).into(),
1667 authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
1668 credits: vote_state.credits(),
1669 commission: (vote_state.inflation_rewards_commission_bps / 100) as u8,
1670 root_slot: vote_state.root_slot,
1671 recent_timestamp: vote_state.last_timestamp.clone(),
1672 votes,
1673 epoch_voting_history,
1674 use_lamports_unit,
1675 use_csv,
1676 epoch_rewards,
1677 inflation_rewards_commission_bps: vote_state.inflation_rewards_commission_bps,
1678 inflation_rewards_collector: vote_state.inflation_rewards_collector.to_string(),
1679 block_revenue_collector: vote_state.block_revenue_collector.to_string(),
1680 block_revenue_commission_bps: vote_state.block_revenue_commission_bps,
1681 pending_delegator_rewards: vote_state.pending_delegator_rewards,
1682 bls_pubkey_compressed: vote_state
1683 .bls_pubkey_compressed
1684 .map(|bytes| bs58::encode(bytes).into_string()),
1685 };
1686
1687 Ok(config.output_format.formatted_string(&vote_account_data))
1688}
1689
1690#[allow(clippy::too_many_arguments)]
1691pub async fn process_withdraw_from_vote_account(
1692 rpc_client: &RpcClient,
1693 config: &CliConfig<'_>,
1694 vote_account_pubkey: &Pubkey,
1695 withdraw_authority: SignerIndex,
1696 withdraw_amount: SpendAmount,
1697 destination_account_pubkey: &Pubkey,
1698 sign_only: bool,
1699 dump_transaction_message: bool,
1700 blockhash_query: &BlockhashQuery,
1701 nonce_account: Option<&Pubkey>,
1702 nonce_authority: SignerIndex,
1703 memo: Option<&String>,
1704 fee_payer: SignerIndex,
1705 compute_unit_price: Option<u64>,
1706) -> ProcessResult {
1707 let withdraw_authority = config.signers[withdraw_authority];
1708 let recent_blockhash = blockhash_query
1709 .get_blockhash(rpc_client, config.commitment)
1710 .await?;
1711
1712 let fee_payer = config.signers[fee_payer];
1713 let nonce_authority = config.signers[nonce_authority];
1714
1715 let compute_unit_limit = match blockhash_query {
1716 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1717 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1718 };
1719 let build_message = |lamports| {
1720 let ixs = vec![withdraw(
1721 vote_account_pubkey,
1722 &withdraw_authority.pubkey(),
1723 lamports,
1724 destination_account_pubkey,
1725 )]
1726 .with_memo(memo)
1727 .with_compute_unit_config(&ComputeUnitConfig {
1728 compute_unit_price,
1729 compute_unit_limit,
1730 });
1731
1732 if let Some(nonce_account) = &nonce_account {
1733 Message::new_with_nonce(
1734 ixs,
1735 Some(&fee_payer.pubkey()),
1736 nonce_account,
1737 &nonce_authority.pubkey(),
1738 )
1739 } else {
1740 Message::new(&ixs, Some(&fee_payer.pubkey()))
1741 }
1742 };
1743
1744 let (message, _) = resolve_spend_tx_and_check_account_balances(
1745 rpc_client,
1746 sign_only,
1747 withdraw_amount,
1748 &recent_blockhash,
1749 vote_account_pubkey,
1750 &fee_payer.pubkey(),
1751 compute_unit_limit,
1752 build_message,
1753 config.commitment,
1754 )
1755 .await?;
1756
1757 if !sign_only {
1758 let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1759 let minimum_balance = rpc_client
1760 .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
1761 .await?;
1762 if let SpendAmount::Some(withdraw_amount) = withdraw_amount {
1763 let balance_remaining = current_balance.saturating_sub(withdraw_amount);
1764 if balance_remaining < minimum_balance && balance_remaining != 0 {
1765 return Err(CliError::BadParameter(format!(
1766 "Withdraw amount too large. The vote account balance must be at least {} SOL \
1767 to remain rent exempt",
1768 build_balance_message(minimum_balance, false, false)
1769 ))
1770 .into());
1771 }
1772 }
1773 }
1774
1775 let mut tx = Transaction::new_unsigned(message);
1776
1777 if sign_only {
1778 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1779 return_signers_with_config(
1780 &tx,
1781 &config.output_format,
1782 &ReturnSignersConfig {
1783 dump_transaction_message,
1784 },
1785 )
1786 } else {
1787 tx.try_sign(&config.signers, recent_blockhash)?;
1788 if let Some(nonce_account) = &nonce_account {
1789 let nonce_account =
1790 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1791 rpc_client,
1792 nonce_account,
1793 config.commitment,
1794 )
1795 .await?;
1796 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1797 }
1798 check_account_for_fee_with_commitment(
1799 rpc_client,
1800 &tx.message.account_keys[0],
1801 &tx.message,
1802 config.commitment,
1803 )
1804 .await?;
1805 let result = rpc_client
1806 .send_and_confirm_transaction_with_spinner_and_config(
1807 &tx,
1808 config.commitment,
1809 config.send_transaction_config,
1810 )
1811 .await;
1812 log_instruction_custom_error::<VoteError>(result, config)
1813 }
1814}
1815
1816pub async fn process_close_vote_account(
1817 rpc_client: &RpcClient,
1818 config: &CliConfig<'_>,
1819 vote_account_pubkey: &Pubkey,
1820 withdraw_authority: SignerIndex,
1821 destination_account_pubkey: &Pubkey,
1822 memo: Option<&String>,
1823 fee_payer: SignerIndex,
1824 compute_unit_price: Option<u64>,
1825) -> ProcessResult {
1826 let vote_account_status = rpc_client
1827 .get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1828 vote_pubkey: Some(vote_account_pubkey.to_string()),
1829 ..RpcGetVoteAccountsConfig::default()
1830 })
1831 .await?;
1832
1833 if let Some(vote_account) = vote_account_status
1834 .current
1835 .into_iter()
1836 .chain(vote_account_status.delinquent)
1837 .next()
1838 {
1839 if vote_account.activated_stake != 0 {
1840 return Err(format!(
1841 "Cannot close a vote account with active stake: {vote_account_pubkey}"
1842 )
1843 .into());
1844 }
1845 }
1846
1847 let latest_blockhash = rpc_client.get_latest_blockhash().await?;
1848 let withdraw_authority = config.signers[withdraw_authority];
1849 let fee_payer = config.signers[fee_payer];
1850
1851 let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1852
1853 let compute_unit_limit = ComputeUnitLimit::Simulated;
1854 let ixs = vec![withdraw(
1855 vote_account_pubkey,
1856 &withdraw_authority.pubkey(),
1857 current_balance,
1858 destination_account_pubkey,
1859 )]
1860 .with_memo(memo)
1861 .with_compute_unit_config(&ComputeUnitConfig {
1862 compute_unit_price,
1863 compute_unit_limit,
1864 });
1865
1866 let mut message = Message::new(&ixs, Some(&fee_payer.pubkey()));
1867 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1868 let mut tx = Transaction::new_unsigned(message);
1869 tx.try_sign(&config.signers, latest_blockhash)?;
1870 check_account_for_fee_with_commitment(
1871 rpc_client,
1872 &tx.message.account_keys[0],
1873 &tx.message,
1874 config.commitment,
1875 )
1876 .await?;
1877 let result = rpc_client
1878 .send_and_confirm_transaction_with_spinner_and_config(
1879 &tx,
1880 config.commitment,
1881 config.send_transaction_config,
1882 )
1883 .await;
1884 log_instruction_custom_error::<VoteError>(result, config)
1885}
1886
1887#[cfg(test)]
1888mod tests {
1889 use {
1890 super::*,
1891 crate::{clap_app::get_clap_app, cli::parse_command},
1892 solana_hash::Hash,
1893 solana_keypair::{Keypair, read_keypair_file, write_keypair},
1894 solana_presigner::Presigner,
1895 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
1896 solana_signer::Signer,
1897 tempfile::NamedTempFile,
1898 };
1899
1900 fn make_tmp_file() -> (String, NamedTempFile) {
1901 let tmp_file = NamedTempFile::new().unwrap();
1902 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
1903 }
1904
1905 #[test]
1906 fn test_parse_command() {
1907 let test_commands = get_clap_app("test", "desc", "version");
1908 let keypair = Keypair::new();
1909 let pubkey = keypair.pubkey();
1910 let pubkey_string = pubkey.to_string();
1911 let keypair2 = Keypair::new();
1912 let pubkey2 = keypair2.pubkey();
1913 let pubkey2_string = pubkey2.to_string();
1914 let sig2 = keypair2.sign_message(&[0u8]);
1915 let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
1916
1917 let default_keypair = Keypair::new();
1918 let (default_keypair_file, mut tmp_file) = make_tmp_file();
1919 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
1920 let default_signer = DefaultSigner::new("", &default_keypair_file);
1921
1922 let blockhash = Hash::default();
1923 let blockhash_string = format!("{blockhash}");
1924 let nonce_account = Pubkey::new_unique();
1925
1926 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1928 "test",
1929 "vote-authorize-voter",
1930 &pubkey_string,
1931 &default_keypair_file,
1932 &pubkey2_string,
1933 ]);
1934 assert_eq!(
1935 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1936 CliCommandInfo {
1937 command: CliCommand::VoteAuthorize {
1938 vote_account_pubkey: pubkey,
1939 new_authorized_pubkey: pubkey2,
1940 vote_authorize: VoteAuthorize::Voter,
1941 use_v2_instruction: false,
1942 sign_only: false,
1943 dump_transaction_message: false,
1944 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1945 nonce_account: None,
1946 nonce_authority: 0,
1947 memo: None,
1948 fee_payer: 0,
1949 authorized: 0,
1950 new_authorized: None,
1951 compute_unit_price: None,
1952 },
1953 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
1954 }
1955 );
1956
1957 let authorized_keypair = Keypair::new();
1958 let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
1959 write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
1960
1961 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1962 "test",
1963 "vote-authorize-voter",
1964 &pubkey_string,
1965 &authorized_keypair_file,
1966 &pubkey2_string,
1967 ]);
1968 assert_eq!(
1969 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1970 CliCommandInfo {
1971 command: CliCommand::VoteAuthorize {
1972 vote_account_pubkey: pubkey,
1973 new_authorized_pubkey: pubkey2,
1974 vote_authorize: VoteAuthorize::Voter,
1975 use_v2_instruction: false,
1976 sign_only: false,
1977 dump_transaction_message: false,
1978 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1979 nonce_account: None,
1980 nonce_authority: 0,
1981 memo: None,
1982 fee_payer: 0,
1983 authorized: 1,
1984 new_authorized: None,
1985 compute_unit_price: None,
1986 },
1987 signers: vec![
1988 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1989 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1990 ],
1991 }
1992 );
1993
1994 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1995 "test",
1996 "vote-authorize-voter",
1997 &pubkey_string,
1998 &authorized_keypair_file,
1999 &pubkey2_string,
2000 "--blockhash",
2001 &blockhash_string,
2002 "--sign-only",
2003 ]);
2004 assert_eq!(
2005 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2006 CliCommandInfo {
2007 command: CliCommand::VoteAuthorize {
2008 vote_account_pubkey: pubkey,
2009 new_authorized_pubkey: pubkey2,
2010 vote_authorize: VoteAuthorize::Voter,
2011 use_v2_instruction: false,
2012 sign_only: true,
2013 dump_transaction_message: false,
2014 blockhash_query: BlockhashQuery::Static(blockhash),
2015 nonce_account: None,
2016 nonce_authority: 0,
2017 memo: None,
2018 fee_payer: 0,
2019 authorized: 1,
2020 new_authorized: None,
2021 compute_unit_price: None,
2022 },
2023 signers: vec![
2024 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2025 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2026 ],
2027 }
2028 );
2029
2030 let authorized_sig = authorized_keypair.sign_message(&[0u8]);
2031 let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig);
2032 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2033 "test",
2034 "vote-authorize-voter",
2035 &pubkey_string,
2036 &authorized_keypair.pubkey().to_string(),
2037 &pubkey2_string,
2038 "--blockhash",
2039 &blockhash_string,
2040 "--signer",
2041 &authorized_signer,
2042 "--signer",
2043 &signer2,
2044 "--fee-payer",
2045 &pubkey2_string,
2046 "--nonce",
2047 &nonce_account.to_string(),
2048 "--nonce-authority",
2049 &pubkey2_string,
2050 ]);
2051 assert_eq!(
2052 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2053 CliCommandInfo {
2054 command: CliCommand::VoteAuthorize {
2055 vote_account_pubkey: pubkey,
2056 new_authorized_pubkey: pubkey2,
2057 vote_authorize: VoteAuthorize::Voter,
2058 use_v2_instruction: false,
2059 sign_only: false,
2060 dump_transaction_message: false,
2061 blockhash_query: BlockhashQuery::Validated(
2062 Source::NonceAccount(nonce_account),
2063 blockhash
2064 ),
2065 nonce_account: Some(nonce_account),
2066 nonce_authority: 0,
2067 memo: None,
2068 fee_payer: 0,
2069 authorized: 1,
2070 new_authorized: None,
2071 compute_unit_price: None,
2072 },
2073 signers: vec![
2074 Box::new(Presigner::new(&pubkey2, &sig2)),
2075 Box::new(Presigner::new(
2076 &authorized_keypair.pubkey(),
2077 &authorized_sig
2078 )),
2079 ],
2080 }
2081 );
2082
2083 let (voter_keypair_file, mut tmp_file) = make_tmp_file();
2085 let voter_keypair = Keypair::new();
2086 write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
2087
2088 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2089 "test",
2090 "vote-authorize-voter-checked",
2091 &pubkey_string,
2092 &default_keypair_file,
2093 &voter_keypair_file,
2094 ]);
2095 assert_eq!(
2096 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2097 CliCommandInfo {
2098 command: CliCommand::VoteAuthorize {
2099 vote_account_pubkey: pubkey,
2100 new_authorized_pubkey: voter_keypair.pubkey(),
2101 vote_authorize: VoteAuthorize::Voter,
2102 use_v2_instruction: false,
2103 sign_only: false,
2104 dump_transaction_message: false,
2105 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2106 nonce_account: None,
2107 nonce_authority: 0,
2108 memo: None,
2109 fee_payer: 0,
2110 authorized: 0,
2111 new_authorized: Some(1),
2112 compute_unit_price: None,
2113 },
2114 signers: vec![
2115 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2116 Box::new(read_keypair_file(&voter_keypair_file).unwrap())
2117 ],
2118 }
2119 );
2120
2121 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2122 "test",
2123 "vote-authorize-voter-checked",
2124 &pubkey_string,
2125 &authorized_keypair_file,
2126 &voter_keypair_file,
2127 ]);
2128 assert_eq!(
2129 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2130 CliCommandInfo {
2131 command: CliCommand::VoteAuthorize {
2132 vote_account_pubkey: pubkey,
2133 new_authorized_pubkey: voter_keypair.pubkey(),
2134 vote_authorize: VoteAuthorize::Voter,
2135 use_v2_instruction: false,
2136 sign_only: false,
2137 dump_transaction_message: false,
2138 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2139 nonce_account: None,
2140 nonce_authority: 0,
2141 memo: None,
2142 fee_payer: 0,
2143 authorized: 1,
2144 new_authorized: Some(2),
2145 compute_unit_price: None,
2146 },
2147 signers: vec![
2148 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2149 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2150 Box::new(read_keypair_file(&voter_keypair_file).unwrap()),
2151 ],
2152 }
2153 );
2154
2155 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2156 "test",
2157 "vote-authorize-voter-checked",
2158 &pubkey_string,
2159 &authorized_keypair_file,
2160 &pubkey2_string,
2161 ]);
2162 assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
2163
2164 let (new_voter_keypair_file, mut tmp_file) = make_tmp_file();
2166 let new_voter_keypair = Keypair::new();
2167 write_keypair(&new_voter_keypair, tmp_file.as_file_mut()).unwrap();
2168
2169 let test_authorize_voter_checked_with_bls = test_commands.clone().get_matches_from(vec![
2170 "test",
2171 "vote-authorize-voter-checked",
2172 &pubkey_string,
2173 &authorized_keypair_file,
2174 &new_voter_keypair_file,
2175 "--use-v2-instruction",
2176 ]);
2177 assert_eq!(
2178 parse_command(
2179 &test_authorize_voter_checked_with_bls,
2180 &default_signer,
2181 &mut None
2182 )
2183 .unwrap(),
2184 CliCommandInfo {
2185 command: CliCommand::VoteAuthorize {
2186 vote_account_pubkey: pubkey,
2187 new_authorized_pubkey: new_voter_keypair.pubkey(),
2188 vote_authorize: VoteAuthorize::Voter,
2189 use_v2_instruction: true,
2190 sign_only: false,
2191 dump_transaction_message: false,
2192 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2193 nonce_account: None,
2194 nonce_authority: 0,
2195 memo: None,
2196 fee_payer: 0,
2197 authorized: 1,
2198 new_authorized: Some(2),
2199 compute_unit_price: None,
2200 },
2201 signers: vec![
2202 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2203 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2204 Box::new(read_keypair_file(&new_voter_keypair_file).unwrap()),
2205 ],
2206 }
2207 );
2208
2209 let (identity_keypair_file, mut tmp_file) = make_tmp_file();
2211 let identity_keypair = Keypair::new();
2212 let authorized_withdrawer = Keypair::new().pubkey();
2213 write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
2214 let (keypair_file, mut tmp_file) = make_tmp_file();
2215 let keypair = Keypair::new();
2216 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2217
2218 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2219 "test",
2220 "create-vote-account",
2221 &keypair_file,
2222 &identity_keypair_file,
2223 &authorized_withdrawer.to_string(),
2224 "--commission",
2225 "10",
2226 ]);
2227 assert_eq!(
2228 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2229 CliCommandInfo {
2230 command: CliCommand::CreateVoteAccount {
2231 vote_account: 1,
2232 seed: None,
2233 identity_account: 2,
2234 authorized_voter: None,
2235 authorized_withdrawer,
2236 commission: Some(10),
2237 use_v2_instruction: false,
2238
2239 inflation_rewards_commission_bps: None,
2240 inflation_rewards_collector: None,
2241 block_revenue_commission_bps: None,
2242 block_revenue_collector: None,
2243 sign_only: false,
2244 dump_transaction_message: false,
2245 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2246 nonce_account: None,
2247 nonce_authority: 0,
2248 memo: None,
2249 fee_payer: 0,
2250 compute_unit_price: None,
2251 },
2252 signers: vec![
2253 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2254 Box::new(read_keypair_file(&keypair_file).unwrap()),
2255 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2256 ],
2257 }
2258 );
2259
2260 let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
2261 "test",
2262 "create-vote-account",
2263 &keypair_file,
2264 &identity_keypair_file,
2265 &authorized_withdrawer.to_string(),
2266 ]);
2267 assert_eq!(
2268 parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
2269 CliCommandInfo {
2270 command: CliCommand::CreateVoteAccount {
2271 vote_account: 1,
2272 seed: None,
2273 identity_account: 2,
2274 authorized_voter: None,
2275 authorized_withdrawer,
2276 commission: None, use_v2_instruction: false,
2278
2279 inflation_rewards_commission_bps: None,
2280 inflation_rewards_collector: None,
2281 block_revenue_commission_bps: None,
2282 block_revenue_collector: None,
2283 sign_only: false,
2284 dump_transaction_message: false,
2285 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2286 nonce_account: None,
2287 nonce_authority: 0,
2288 memo: None,
2289 fee_payer: 0,
2290 compute_unit_price: None,
2291 },
2292 signers: vec![
2293 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2294 Box::new(read_keypair_file(&keypair_file).unwrap()),
2295 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2296 ],
2297 }
2298 );
2299
2300 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2301 "test",
2302 "create-vote-account",
2303 &keypair_file,
2304 &identity_keypair_file,
2305 &authorized_withdrawer.to_string(),
2306 "--commission",
2307 "10",
2308 "--blockhash",
2309 &blockhash_string,
2310 "--sign-only",
2311 "--fee-payer",
2312 &default_keypair.pubkey().to_string(),
2313 ]);
2314 assert_eq!(
2315 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2316 CliCommandInfo {
2317 command: CliCommand::CreateVoteAccount {
2318 vote_account: 1,
2319 seed: None,
2320 identity_account: 2,
2321 authorized_voter: None,
2322 authorized_withdrawer,
2323 commission: Some(10), use_v2_instruction: false,
2325
2326 inflation_rewards_commission_bps: None,
2327 inflation_rewards_collector: None,
2328 block_revenue_commission_bps: None,
2329 block_revenue_collector: None,
2330 sign_only: true,
2331 dump_transaction_message: false,
2332 blockhash_query: BlockhashQuery::Static(blockhash),
2333 nonce_account: None,
2334 nonce_authority: 0,
2335 memo: None,
2336 fee_payer: 0,
2337 compute_unit_price: None,
2338 },
2339 signers: vec![
2340 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2341 Box::new(read_keypair_file(&keypair_file).unwrap()),
2342 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2343 ],
2344 }
2345 );
2346
2347 let identity_sig = identity_keypair.sign_message(&[0u8]);
2348 let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig);
2349 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2350 "test",
2351 "create-vote-account",
2352 &keypair_file,
2353 &identity_keypair.pubkey().to_string(),
2354 &authorized_withdrawer.to_string(),
2355 "--commission",
2356 "10",
2357 "--blockhash",
2358 &blockhash_string,
2359 "--signer",
2360 &identity_signer,
2361 "--signer",
2362 &signer2,
2363 "--fee-payer",
2364 &default_keypair_file,
2365 "--nonce",
2366 &nonce_account.to_string(),
2367 "--nonce-authority",
2368 &pubkey2_string,
2369 ]);
2370 assert_eq!(
2371 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2372 CliCommandInfo {
2373 command: CliCommand::CreateVoteAccount {
2374 vote_account: 1,
2375 seed: None,
2376 identity_account: 2,
2377 authorized_voter: None,
2378 authorized_withdrawer,
2379 commission: Some(10),
2380 use_v2_instruction: false,
2381
2382 inflation_rewards_commission_bps: None,
2383 inflation_rewards_collector: None,
2384 block_revenue_commission_bps: None,
2385 block_revenue_collector: None,
2386 sign_only: false,
2387 dump_transaction_message: false,
2388 blockhash_query: BlockhashQuery::Validated(
2389 Source::NonceAccount(nonce_account),
2390 blockhash
2391 ),
2392 nonce_account: Some(nonce_account),
2393 nonce_authority: 3,
2394 memo: None,
2395 fee_payer: 0,
2396 compute_unit_price: None,
2397 },
2398 signers: vec![
2399 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2400 Box::new(read_keypair_file(&keypair_file).unwrap()),
2401 Box::new(Presigner::new(&identity_keypair.pubkey(), &identity_sig)),
2402 Box::new(Presigner::new(&pubkey2, &sig2)),
2403 ],
2404 }
2405 );
2406
2407 let authed = solana_pubkey::new_rand();
2409 let (keypair_file, mut tmp_file) = make_tmp_file();
2410 let keypair = Keypair::new();
2411 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2412
2413 let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
2414 "test",
2415 "create-vote-account",
2416 &keypair_file,
2417 &identity_keypair_file,
2418 &authorized_withdrawer.to_string(),
2419 "--authorized-voter",
2420 &authed.to_string(),
2421 ]);
2422 assert_eq!(
2423 parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
2424 CliCommandInfo {
2425 command: CliCommand::CreateVoteAccount {
2426 vote_account: 1,
2427 seed: None,
2428 identity_account: 2,
2429 authorized_voter: Some(authed),
2430 authorized_withdrawer,
2431 commission: None, use_v2_instruction: false,
2433
2434 inflation_rewards_commission_bps: None,
2435 inflation_rewards_collector: None,
2436 block_revenue_commission_bps: None,
2437 block_revenue_collector: None,
2438 sign_only: false,
2439 dump_transaction_message: false,
2440 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2441 nonce_account: None,
2442 nonce_authority: 0,
2443 memo: None,
2444 fee_payer: 0,
2445 compute_unit_price: None,
2446 },
2447 signers: vec![
2448 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2449 Box::new(keypair),
2450 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2451 ],
2452 }
2453 );
2454
2455 let (keypair_file, mut tmp_file) = make_tmp_file();
2456 let keypair = Keypair::new();
2457 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2458 let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
2460 "test",
2461 "create-vote-account",
2462 &keypair_file,
2463 &identity_keypair_file,
2464 &identity_keypair_file,
2465 "--allow-unsafe-authorized-withdrawer",
2466 ]);
2467 assert_eq!(
2468 parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
2469 CliCommandInfo {
2470 command: CliCommand::CreateVoteAccount {
2471 vote_account: 1,
2472 seed: None,
2473 identity_account: 2,
2474 authorized_voter: None,
2475 authorized_withdrawer: identity_keypair.pubkey(),
2476 commission: None, use_v2_instruction: false,
2478
2479 inflation_rewards_commission_bps: None,
2480 inflation_rewards_collector: None,
2481 block_revenue_commission_bps: None,
2482 block_revenue_collector: None,
2483 sign_only: false,
2484 dump_transaction_message: false,
2485 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2486 nonce_account: None,
2487 nonce_authority: 0,
2488 memo: None,
2489 fee_payer: 0,
2490 compute_unit_price: None,
2491 },
2492 signers: vec![
2493 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2494 Box::new(read_keypair_file(&keypair_file).unwrap()),
2495 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2496 ],
2497 }
2498 );
2499
2500 let (keypair_file, mut tmp_file) = make_tmp_file();
2502 let keypair = Keypair::new();
2503 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2504 let inflation_rewards_collector = Keypair::new().pubkey();
2505 let block_revenue_collector = Keypair::new().pubkey();
2506
2507 let test_create_vote_account_v2 = test_commands.clone().get_matches_from(vec![
2509 "test",
2510 "create-vote-account",
2511 &keypair_file,
2512 &identity_keypair_file,
2513 &authorized_withdrawer.to_string(),
2514 "--use-v2-instruction",
2515 "--authorized-voter",
2516 &authed.to_string(),
2517 "--inflation-rewards-commission-bps",
2518 "500",
2519 "--inflation-rewards-collector",
2520 &inflation_rewards_collector.to_string(),
2521 "--block-revenue-commission-bps",
2522 "1000",
2523 "--block-revenue-collector",
2524 &block_revenue_collector.to_string(),
2525 ]);
2526 assert_eq!(
2527 parse_command(&test_create_vote_account_v2, &default_signer, &mut None).unwrap(),
2528 CliCommandInfo {
2529 command: CliCommand::CreateVoteAccount {
2530 vote_account: 1,
2531 seed: None,
2532 identity_account: 2,
2533 authorized_voter: Some(authed),
2534 authorized_withdrawer,
2535 commission: None,
2536 use_v2_instruction: true,
2537 inflation_rewards_commission_bps: Some(500),
2538 inflation_rewards_collector: Some(inflation_rewards_collector),
2539 block_revenue_commission_bps: Some(1000),
2540 block_revenue_collector: Some(block_revenue_collector),
2541 sign_only: false,
2542 dump_transaction_message: false,
2543 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2544 nonce_account: None,
2545 nonce_authority: 0,
2546 memo: None,
2547 fee_payer: 0,
2548 compute_unit_price: None,
2549 },
2550 signers: vec![
2551 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2552 Box::new(read_keypair_file(&keypair_file).unwrap()),
2553 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2554 ],
2555 }
2556 );
2557
2558 let (keypair_file, mut tmp_file) = make_tmp_file();
2560 let sign_only_vote_keypair = Keypair::new();
2561 write_keypair(&sign_only_vote_keypair, tmp_file.as_file_mut()).unwrap();
2562
2563 let test_create_vote_account_v2_sign_only = test_commands.clone().get_matches_from(vec![
2564 "test",
2565 "create-vote-account",
2566 &keypair_file,
2567 &identity_keypair_file,
2568 &authorized_withdrawer.to_string(),
2569 "--use-v2-instruction",
2570 "--blockhash",
2571 &blockhash_string,
2572 "--sign-only",
2573 ]);
2574 assert_eq!(
2575 parse_command(
2576 &test_create_vote_account_v2_sign_only,
2577 &default_signer,
2578 &mut None
2579 )
2580 .unwrap(),
2581 CliCommandInfo {
2582 command: CliCommand::CreateVoteAccount {
2583 vote_account: 1,
2584 seed: None,
2585 identity_account: 2,
2586 authorized_voter: None,
2587 authorized_withdrawer,
2588 commission: None,
2589 use_v2_instruction: true,
2590
2591 inflation_rewards_commission_bps: None,
2592 inflation_rewards_collector: None,
2593 block_revenue_commission_bps: None,
2594 block_revenue_collector: None,
2595 sign_only: true,
2596 dump_transaction_message: false,
2597 blockhash_query: BlockhashQuery::Static(blockhash),
2598 nonce_account: None,
2599 nonce_authority: 0,
2600 memo: None,
2601 fee_payer: 0,
2602 compute_unit_price: None,
2603 },
2604 signers: vec![
2605 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2606 Box::new(read_keypair_file(&keypair_file).unwrap()),
2607 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2608 ],
2609 }
2610 );
2611
2612 let (keypair_file, mut tmp_file) = make_tmp_file();
2614 let conflict_vote_keypair = Keypair::new();
2615 write_keypair(&conflict_vote_keypair, tmp_file.as_file_mut()).unwrap();
2616
2617 let test_conflict = test_commands.clone().get_matches_from(vec![
2619 "test",
2620 "create-vote-account",
2621 &keypair_file,
2622 &identity_keypair_file,
2623 &authorized_withdrawer.to_string(),
2624 "--commission",
2625 "10",
2626 "--use-v2-instruction",
2627 ]);
2628 assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2629
2630 let test_conflict = test_commands.clone().get_matches_from(vec![
2632 "test",
2633 "create-vote-account",
2634 &keypair_file,
2635 &identity_keypair_file,
2636 &authorized_withdrawer.to_string(),
2637 "--commission",
2638 "10",
2639 "--inflation-rewards-commission-bps",
2640 "1000",
2641 ]);
2642 assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2643
2644 let test_conflict = test_commands.clone().get_matches_from(vec![
2646 "test",
2647 "create-vote-account",
2648 &keypair_file,
2649 &identity_keypair_file,
2650 &authorized_withdrawer.to_string(),
2651 "--commission",
2652 "10",
2653 "--block-revenue-commission-bps",
2654 "500",
2655 ]);
2656 assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2657
2658 let test_update_validator = test_commands.clone().get_matches_from(vec![
2659 "test",
2660 "vote-update-validator",
2661 &pubkey_string,
2662 &identity_keypair_file,
2663 &keypair_file,
2664 ]);
2665 assert_eq!(
2666 parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
2667 CliCommandInfo {
2668 command: CliCommand::VoteUpdateValidator {
2669 vote_account_pubkey: pubkey,
2670 new_identity_account: 2,
2671 withdraw_authority: 1,
2672 sign_only: false,
2673 dump_transaction_message: false,
2674 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2675 nonce_account: None,
2676 nonce_authority: 0,
2677 memo: None,
2678 fee_payer: 0,
2679 compute_unit_price: None,
2680 },
2681 signers: vec![
2682 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2683 Box::new(read_keypair_file(&keypair_file).unwrap()),
2684 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2685 ],
2686 }
2687 );
2688
2689 let test_update_commission = test_commands.clone().get_matches_from(vec![
2690 "test",
2691 "vote-update-commission",
2692 &pubkey_string,
2693 "42",
2694 &keypair_file,
2695 ]);
2696 assert_eq!(
2697 parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
2698 CliCommandInfo {
2699 command: CliCommand::VoteUpdateCommission {
2700 vote_account_pubkey: pubkey,
2701 commission: 42,
2702 withdraw_authority: 1,
2703 sign_only: false,
2704 dump_transaction_message: false,
2705 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2706 nonce_account: None,
2707 nonce_authority: 0,
2708 memo: None,
2709 fee_payer: 0,
2710 compute_unit_price: None,
2711 },
2712 signers: vec![
2713 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2714 Box::new(read_keypair_file(&keypair_file).unwrap()),
2715 ],
2716 }
2717 );
2718
2719 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2721 "test",
2722 "withdraw-from-vote-account",
2723 &keypair_file,
2724 &pubkey_string,
2725 "42",
2726 ]);
2727 assert_eq!(
2728 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2729 CliCommandInfo {
2730 command: CliCommand::WithdrawFromVoteAccount {
2731 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2732 destination_account_pubkey: pubkey,
2733 withdraw_authority: 0,
2734 withdraw_amount: SpendAmount::Some(42_000_000_000),
2735 sign_only: false,
2736 dump_transaction_message: false,
2737 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2738 nonce_account: None,
2739 nonce_authority: 0,
2740 memo: None,
2741 fee_payer: 0,
2742 compute_unit_price: None,
2743 },
2744 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2745 }
2746 );
2747
2748 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2750 "test",
2751 "withdraw-from-vote-account",
2752 &keypair_file,
2753 &pubkey_string,
2754 "ALL",
2755 ]);
2756 assert_eq!(
2757 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2758 CliCommandInfo {
2759 command: CliCommand::WithdrawFromVoteAccount {
2760 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2761 destination_account_pubkey: pubkey,
2762 withdraw_authority: 0,
2763 withdraw_amount: SpendAmount::RentExempt,
2764 sign_only: false,
2765 dump_transaction_message: false,
2766 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2767 nonce_account: None,
2768 nonce_authority: 0,
2769 memo: None,
2770 fee_payer: 0,
2771 compute_unit_price: None,
2772 },
2773 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2774 }
2775 );
2776
2777 let withdraw_authority = Keypair::new();
2779 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2780 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2781 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2782 "test",
2783 "withdraw-from-vote-account",
2784 &keypair_file,
2785 &pubkey_string,
2786 "42",
2787 "--authorized-withdrawer",
2788 &withdraw_authority_file,
2789 ]);
2790 assert_eq!(
2791 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2792 CliCommandInfo {
2793 command: CliCommand::WithdrawFromVoteAccount {
2794 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2795 destination_account_pubkey: pubkey,
2796 withdraw_authority: 1,
2797 withdraw_amount: SpendAmount::Some(42_000_000_000),
2798 sign_only: false,
2799 dump_transaction_message: false,
2800 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2801 nonce_account: None,
2802 nonce_authority: 0,
2803 memo: None,
2804 fee_payer: 0,
2805 compute_unit_price: None,
2806 },
2807 signers: vec![
2808 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2809 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2810 ],
2811 }
2812 );
2813
2814 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2816 "test",
2817 "withdraw-from-vote-account",
2818 &keypair.pubkey().to_string(),
2819 &pubkey_string,
2820 "42",
2821 "--authorized-withdrawer",
2822 &withdraw_authority_file,
2823 "--blockhash",
2824 &blockhash_string,
2825 "--sign-only",
2826 "--fee-payer",
2827 &withdraw_authority_file,
2828 ]);
2829 assert_eq!(
2830 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2831 CliCommandInfo {
2832 command: CliCommand::WithdrawFromVoteAccount {
2833 vote_account_pubkey: keypair.pubkey(),
2834 destination_account_pubkey: pubkey,
2835 withdraw_authority: 0,
2836 withdraw_amount: SpendAmount::Some(42_000_000_000),
2837 sign_only: true,
2838 dump_transaction_message: false,
2839 blockhash_query: BlockhashQuery::Static(blockhash),
2840 nonce_account: None,
2841 nonce_authority: 0,
2842 memo: None,
2843 fee_payer: 0,
2844 compute_unit_price: None,
2845 },
2846 signers: vec![Box::new(
2847 read_keypair_file(&withdraw_authority_file).unwrap()
2848 )],
2849 }
2850 );
2851
2852 let authorized_sig = withdraw_authority.sign_message(&[0u8]);
2853 let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig);
2854 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2855 "test",
2856 "withdraw-from-vote-account",
2857 &keypair.pubkey().to_string(),
2858 &pubkey_string,
2859 "42",
2860 "--authorized-withdrawer",
2861 &withdraw_authority.pubkey().to_string(),
2862 "--blockhash",
2863 &blockhash_string,
2864 "--signer",
2865 &authorized_signer,
2866 "--fee-payer",
2867 &withdraw_authority.pubkey().to_string(),
2868 ]);
2869 assert_eq!(
2870 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2871 CliCommandInfo {
2872 command: CliCommand::WithdrawFromVoteAccount {
2873 vote_account_pubkey: keypair.pubkey(),
2874 destination_account_pubkey: pubkey,
2875 withdraw_authority: 0,
2876 withdraw_amount: SpendAmount::Some(42_000_000_000),
2877 sign_only: false,
2878 dump_transaction_message: false,
2879 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
2880 nonce_account: None,
2881 nonce_authority: 0,
2882 memo: None,
2883 fee_payer: 0,
2884 compute_unit_price: None,
2885 },
2886 signers: vec![Box::new(Presigner::new(
2887 &withdraw_authority.pubkey(),
2888 &authorized_sig
2889 )),],
2890 }
2891 );
2892
2893 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2895 "test",
2896 "close-vote-account",
2897 &keypair_file,
2898 &pubkey_string,
2899 ]);
2900 assert_eq!(
2901 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2902 CliCommandInfo {
2903 command: CliCommand::CloseVoteAccount {
2904 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2905 destination_account_pubkey: pubkey,
2906 withdraw_authority: 0,
2907 memo: None,
2908 fee_payer: 0,
2909 compute_unit_price: None,
2910 },
2911 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2912 }
2913 );
2914
2915 let withdraw_authority = Keypair::new();
2917 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2918 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2919 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2920 "test",
2921 "close-vote-account",
2922 &keypair_file,
2923 &pubkey_string,
2924 "--authorized-withdrawer",
2925 &withdraw_authority_file,
2926 ]);
2927 assert_eq!(
2928 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2929 CliCommandInfo {
2930 command: CliCommand::CloseVoteAccount {
2931 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2932 destination_account_pubkey: pubkey,
2933 withdraw_authority: 1,
2934 memo: None,
2935 fee_payer: 0,
2936 compute_unit_price: None,
2937 },
2938 signers: vec![
2939 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2940 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2941 ],
2942 }
2943 );
2944
2945 let withdraw_authority = Keypair::new();
2947 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2948 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2949 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2950 "test",
2951 "close-vote-account",
2952 &keypair_file,
2953 &pubkey_string,
2954 "--authorized-withdrawer",
2955 &withdraw_authority_file,
2956 "--with-compute-unit-price",
2957 "99",
2958 ]);
2959 assert_eq!(
2960 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2961 CliCommandInfo {
2962 command: CliCommand::CloseVoteAccount {
2963 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2964 destination_account_pubkey: pubkey,
2965 withdraw_authority: 1,
2966 memo: None,
2967 fee_payer: 0,
2968 compute_unit_price: Some(99),
2969 },
2970 signers: vec![
2971 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2972 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2973 ],
2974 }
2975 );
2976 }
2977}