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 block_revenue_commission_bps: block_revenue_commission_bps.unwrap_or(10000),
1044 };
1045 let inflation_rewards_collector_key = inflation_rewards_collector
1046 .copied()
1047 .unwrap_or(vote_account_address);
1048 let block_revenue_collector_key =
1049 block_revenue_collector.copied().unwrap_or(identity_pubkey);
1050 vote_instruction::create_account_with_config_v2(
1051 &config.signers[0].pubkey(),
1052 to,
1053 &vote_init,
1054 &inflation_rewards_collector_key,
1055 &block_revenue_collector_key,
1056 lamports,
1057 create_vote_account_config,
1058 )
1059 } else {
1060 let vote_init = VoteInit {
1061 node_pubkey: identity_pubkey,
1062 authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
1063 authorized_withdrawer,
1064 commission: commission.unwrap_or(100),
1065 };
1066 vote_instruction::create_account_with_config(
1067 &config.signers[0].pubkey(),
1068 to,
1069 &vote_init,
1070 lamports,
1071 create_vote_account_config,
1072 )
1073 };
1074
1075 let ixs = ixs
1076 .with_memo(memo)
1077 .with_compute_unit_config(&ComputeUnitConfig {
1078 compute_unit_price,
1079 compute_unit_limit,
1080 });
1081
1082 if let Some(nonce_account) = &nonce_account {
1083 Message::new_with_nonce(
1084 ixs,
1085 Some(&fee_payer.pubkey()),
1086 nonce_account,
1087 &nonce_authority.pubkey(),
1088 )
1089 } else {
1090 Message::new(&ixs, Some(&fee_payer.pubkey()))
1091 }
1092 };
1093
1094 let recent_blockhash = blockhash_query
1095 .get_blockhash(rpc_client, config.commitment)
1096 .await?;
1097
1098 let (message, _) = resolve_spend_tx_and_check_account_balances(
1099 rpc_client,
1100 sign_only,
1101 amount,
1102 &recent_blockhash,
1103 &config.signers[0].pubkey(),
1104 &fee_payer.pubkey(),
1105 compute_unit_limit,
1106 build_message,
1107 config.commitment,
1108 )
1109 .await?;
1110
1111 if !sign_only {
1112 if let Ok(response) = rpc_client
1113 .get_account_with_commitment(&vote_account_address, config.commitment)
1114 .await
1115 {
1116 if let Some(vote_account) = response.value {
1117 let err_msg = if vote_account.owner == solana_vote_program::id() {
1118 format!("Vote account {vote_account_address} already exists")
1119 } else {
1120 format!(
1121 "Account {vote_account_address} already exists and is not a vote account"
1122 )
1123 };
1124 return Err(CliError::BadParameter(err_msg).into());
1125 }
1126 }
1127
1128 if let Some(nonce_account) = &nonce_account {
1129 let nonce_account =
1130 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1131 rpc_client,
1132 nonce_account,
1133 config.commitment,
1134 )
1135 .await?;
1136 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1137 }
1138 }
1139
1140 let mut tx = Transaction::new_unsigned(message);
1141 if sign_only {
1142 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1143 return_signers_with_config(
1144 &tx,
1145 &config.output_format,
1146 &ReturnSignersConfig {
1147 dump_transaction_message,
1148 },
1149 )
1150 } else {
1151 tx.try_sign(&config.signers, recent_blockhash)?;
1152 let result = rpc_client
1153 .send_and_confirm_transaction_with_spinner_and_config(
1154 &tx,
1155 config.commitment,
1156 config.send_transaction_config,
1157 )
1158 .await;
1159 log_instruction_custom_error::<SystemError>(result, config)
1160 }
1161}
1162
1163#[allow(clippy::too_many_arguments)]
1164pub async fn process_vote_authorize(
1165 rpc_client: &RpcClient,
1166 config: &CliConfig<'_>,
1167 vote_account_pubkey: &Pubkey,
1168 new_authorized_pubkey: &Pubkey,
1169 vote_authorize: VoteAuthorize,
1170 use_v2_instruction: bool,
1171 authorized: SignerIndex,
1172 new_authorized: Option<SignerIndex>,
1173 sign_only: bool,
1174 dump_transaction_message: bool,
1175 blockhash_query: &BlockhashQuery,
1176 nonce_account: Option<Pubkey>,
1177 nonce_authority: SignerIndex,
1178 memo: Option<&String>,
1179 fee_payer: SignerIndex,
1180 compute_unit_price: Option<u64>,
1181) -> ProcessResult {
1182 let authorized = config.signers[authorized];
1183 let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
1184 let is_checked = new_authorized_signer.is_some();
1185
1186 let vote_state = if !sign_only {
1187 Some(
1188 get_vote_account(rpc_client, vote_account_pubkey, config.commitment)
1189 .await?
1190 .1,
1191 )
1192 } else {
1193 None
1194 };
1195
1196 let use_bls = if !matches!(vote_authorize, VoteAuthorize::Voter) {
1204 false
1206 } else if use_v2_instruction {
1207 true
1209 } else if sign_only {
1210 false
1212 } else if vote_state
1213 .as_ref()
1214 .map(|vs| vs.bls_pubkey_compressed.is_some())
1215 .unwrap_or(false)
1216 {
1217 true
1219 } else {
1220 get_feature_is_active(rpc_client, &bls_pubkey_management_in_vote_account::id())
1222 .await
1223 .unwrap_or(false)
1224 };
1225
1226 match vote_authorize {
1227 VoteAuthorize::Voter => {
1228 if let Some(vote_state) = vote_state {
1229 let current_epoch = rpc_client.get_epoch_info().await?.epoch;
1230 let current_authorized_voter = vote_state
1231 .authorized_voters
1232 .get_authorized_voter(current_epoch)
1233 .ok_or_else(|| {
1234 CliError::RpcRequestError(
1235 "Invalid vote account state; no authorized voters found".to_string(),
1236 )
1237 })?;
1238 check_current_authority(
1239 &[current_authorized_voter, vote_state.authorized_withdrawer],
1240 &authorized.pubkey(),
1241 )?;
1242 if let Some(signer) = new_authorized_signer {
1243 if signer.is_interactive() {
1244 return Err(CliError::BadParameter(format!(
1245 "invalid new authorized vote signer {new_authorized_pubkey:?}. \
1246 Interactive vote signers not supported"
1247 ))
1248 .into());
1249 }
1250 }
1251 }
1252 }
1253 VoteAuthorize::Withdrawer => {
1254 check_unique_pubkeys(
1255 (&authorized.pubkey(), "authorized_account".to_string()),
1256 (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
1257 )?;
1258 if let Some(vote_state) = vote_state {
1259 check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())?
1260 }
1261 }
1262 VoteAuthorize::VoterWithBLS(_) => {
1263 unreachable!("VoterWithBLS should not be passed as vote_authorize parameter");
1266 }
1267 }
1268
1269 let effective_vote_authorize = if use_bls {
1272 if !is_checked {
1273 return Err(CliError::BadParameter(
1274 "BLS key derivation requires the new voter to be a signer. Use \
1275 `vote-authorize-voter-checked` instead."
1276 .to_owned(),
1277 )
1278 .into());
1279 }
1280 let new_authorized_signer = new_authorized_signer.unwrap();
1281 let derived_bls_keypair =
1282 BLSKeypair::derive_from_signer(new_authorized_signer, BLS_KEYPAIR_DERIVE_SEED)
1283 .map_err(|e| {
1284 CliError::BadParameter(format!("Failed to derive BLS keypair: {e}"))
1285 })?;
1286 let (bls_pubkey, bls_proof_of_possession) =
1287 create_bls_proof_of_possession(vote_account_pubkey, &derived_bls_keypair);
1288 VoteAuthorize::VoterWithBLS(VoterWithBLSArgs {
1289 bls_pubkey,
1290 bls_proof_of_possession,
1291 })
1292 } else {
1293 vote_authorize
1294 };
1295
1296 let vote_ix = if is_checked {
1297 vote_instruction::authorize_checked(
1298 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, effective_vote_authorize, )
1303 } else {
1304 vote_instruction::authorize(
1305 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, effective_vote_authorize, )
1310 };
1311
1312 let compute_unit_limit = match blockhash_query {
1313 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1314 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1315 };
1316 let ixs = vec![vote_ix]
1317 .with_memo(memo)
1318 .with_compute_unit_config(&ComputeUnitConfig {
1319 compute_unit_price,
1320 compute_unit_limit,
1321 });
1322
1323 let recent_blockhash = blockhash_query
1324 .get_blockhash(rpc_client, config.commitment)
1325 .await?;
1326
1327 let nonce_authority = config.signers[nonce_authority];
1328 let fee_payer = config.signers[fee_payer];
1329
1330 let mut message = if let Some(nonce_account) = &nonce_account {
1331 Message::new_with_nonce(
1332 ixs,
1333 Some(&fee_payer.pubkey()),
1334 nonce_account,
1335 &nonce_authority.pubkey(),
1336 )
1337 } else {
1338 Message::new(&ixs, Some(&fee_payer.pubkey()))
1339 };
1340 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1341 let mut tx = Transaction::new_unsigned(message);
1342
1343 if sign_only {
1344 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1345 return_signers_with_config(
1346 &tx,
1347 &config.output_format,
1348 &ReturnSignersConfig {
1349 dump_transaction_message,
1350 },
1351 )
1352 } else {
1353 tx.try_sign(&config.signers, recent_blockhash)?;
1354 if let Some(nonce_account) = &nonce_account {
1355 let nonce_account =
1356 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1357 rpc_client,
1358 nonce_account,
1359 config.commitment,
1360 )
1361 .await?;
1362 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1363 }
1364 check_account_for_fee_with_commitment(
1365 rpc_client,
1366 &config.signers[0].pubkey(),
1367 &tx.message,
1368 config.commitment,
1369 )
1370 .await?;
1371 let result = rpc_client
1372 .send_and_confirm_transaction_with_spinner_and_config(
1373 &tx,
1374 config.commitment,
1375 config.send_transaction_config,
1376 )
1377 .await;
1378 log_instruction_custom_error::<VoteError>(result, config)
1379 }
1380}
1381
1382#[allow(clippy::too_many_arguments)]
1383pub async fn process_vote_update_validator(
1384 rpc_client: &RpcClient,
1385 config: &CliConfig<'_>,
1386 vote_account_pubkey: &Pubkey,
1387 new_identity_account: SignerIndex,
1388 withdraw_authority: SignerIndex,
1389 sign_only: bool,
1390 dump_transaction_message: bool,
1391 blockhash_query: &BlockhashQuery,
1392 nonce_account: Option<Pubkey>,
1393 nonce_authority: SignerIndex,
1394 memo: Option<&String>,
1395 fee_payer: SignerIndex,
1396 compute_unit_price: Option<u64>,
1397) -> ProcessResult {
1398 let authorized_withdrawer = config.signers[withdraw_authority];
1399 let new_identity_account = config.signers[new_identity_account];
1400 let new_identity_pubkey = new_identity_account.pubkey();
1401 check_unique_pubkeys(
1402 (vote_account_pubkey, "vote_account_pubkey".to_string()),
1403 (&new_identity_pubkey, "new_identity_account".to_string()),
1404 )?;
1405 let recent_blockhash = blockhash_query
1406 .get_blockhash(rpc_client, config.commitment)
1407 .await?;
1408 let compute_unit_limit = match blockhash_query {
1409 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1410 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1411 };
1412 let ixs = vec![vote_instruction::update_validator_identity(
1413 vote_account_pubkey,
1414 &authorized_withdrawer.pubkey(),
1415 &new_identity_pubkey,
1416 )]
1417 .with_memo(memo)
1418 .with_compute_unit_config(&ComputeUnitConfig {
1419 compute_unit_price,
1420 compute_unit_limit,
1421 });
1422 let nonce_authority = config.signers[nonce_authority];
1423 let fee_payer = config.signers[fee_payer];
1424
1425 let mut message = if let Some(nonce_account) = &nonce_account {
1426 Message::new_with_nonce(
1427 ixs,
1428 Some(&fee_payer.pubkey()),
1429 nonce_account,
1430 &nonce_authority.pubkey(),
1431 )
1432 } else {
1433 Message::new(&ixs, Some(&fee_payer.pubkey()))
1434 };
1435 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1436 let mut tx = Transaction::new_unsigned(message);
1437
1438 if sign_only {
1439 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1440 return_signers_with_config(
1441 &tx,
1442 &config.output_format,
1443 &ReturnSignersConfig {
1444 dump_transaction_message,
1445 },
1446 )
1447 } else {
1448 tx.try_sign(&config.signers, recent_blockhash)?;
1449 if let Some(nonce_account) = &nonce_account {
1450 let nonce_account =
1451 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1452 rpc_client,
1453 nonce_account,
1454 config.commitment,
1455 )
1456 .await?;
1457 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1458 }
1459 check_account_for_fee_with_commitment(
1460 rpc_client,
1461 &config.signers[0].pubkey(),
1462 &tx.message,
1463 config.commitment,
1464 )
1465 .await?;
1466 let result = rpc_client
1467 .send_and_confirm_transaction_with_spinner_and_config(
1468 &tx,
1469 config.commitment,
1470 config.send_transaction_config,
1471 )
1472 .await;
1473 log_instruction_custom_error::<VoteError>(result, config)
1474 }
1475}
1476
1477#[allow(clippy::too_many_arguments)]
1478pub async fn process_vote_update_commission(
1479 rpc_client: &RpcClient,
1480 config: &CliConfig<'_>,
1481 vote_account_pubkey: &Pubkey,
1482 commission: u8,
1483 withdraw_authority: SignerIndex,
1484 sign_only: bool,
1485 dump_transaction_message: bool,
1486 blockhash_query: &BlockhashQuery,
1487 nonce_account: Option<Pubkey>,
1488 nonce_authority: SignerIndex,
1489 memo: Option<&String>,
1490 fee_payer: SignerIndex,
1491 compute_unit_price: Option<u64>,
1492) -> ProcessResult {
1493 let authorized_withdrawer = config.signers[withdraw_authority];
1494 let recent_blockhash = blockhash_query
1495 .get_blockhash(rpc_client, config.commitment)
1496 .await?;
1497 let compute_unit_limit = match blockhash_query {
1498 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1499 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1500 };
1501 let ixs = vec![vote_instruction::update_commission(
1502 vote_account_pubkey,
1503 &authorized_withdrawer.pubkey(),
1504 commission,
1505 )]
1506 .with_memo(memo)
1507 .with_compute_unit_config(&ComputeUnitConfig {
1508 compute_unit_price,
1509 compute_unit_limit,
1510 });
1511 let nonce_authority = config.signers[nonce_authority];
1512 let fee_payer = config.signers[fee_payer];
1513
1514 let mut message = if let Some(nonce_account) = &nonce_account {
1515 Message::new_with_nonce(
1516 ixs,
1517 Some(&fee_payer.pubkey()),
1518 nonce_account,
1519 &nonce_authority.pubkey(),
1520 )
1521 } else {
1522 Message::new(&ixs, Some(&fee_payer.pubkey()))
1523 };
1524 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1525 let mut tx = Transaction::new_unsigned(message);
1526 if sign_only {
1527 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1528 return_signers_with_config(
1529 &tx,
1530 &config.output_format,
1531 &ReturnSignersConfig {
1532 dump_transaction_message,
1533 },
1534 )
1535 } else {
1536 tx.try_sign(&config.signers, recent_blockhash)?;
1537 if let Some(nonce_account) = &nonce_account {
1538 let nonce_account =
1539 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1540 rpc_client,
1541 nonce_account,
1542 config.commitment,
1543 )
1544 .await?;
1545 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1546 }
1547 check_account_for_fee_with_commitment(
1548 rpc_client,
1549 &config.signers[0].pubkey(),
1550 &tx.message,
1551 config.commitment,
1552 )
1553 .await?;
1554 let result = rpc_client
1555 .send_and_confirm_transaction_with_spinner_and_config(
1556 &tx,
1557 config.commitment,
1558 config.send_transaction_config,
1559 )
1560 .await;
1561 log_instruction_custom_error::<VoteError>(result, config)
1562 }
1563}
1564
1565pub(crate) async fn get_vote_account(
1566 rpc_client: &RpcClient,
1567 vote_account_pubkey: &Pubkey,
1568 commitment_config: CommitmentConfig,
1569) -> Result<(Account, VoteStateV4), Box<dyn std::error::Error>> {
1570 let vote_account = rpc_client
1571 .get_account_with_commitment(vote_account_pubkey, commitment_config)
1572 .await?
1573 .value
1574 .ok_or_else(|| {
1575 CliError::RpcRequestError(format!("{vote_account_pubkey:?} account does not exist"))
1576 })?;
1577
1578 if vote_account.owner != solana_vote_program::id() {
1579 return Err(CliError::RpcRequestError(format!(
1580 "{vote_account_pubkey:?} is not a vote account"
1581 ))
1582 .into());
1583 }
1584 let vote_state =
1585 VoteStateV4::deserialize(&vote_account.data, vote_account_pubkey).map_err(|_| {
1586 CliError::RpcRequestError(
1587 "Account data could not be deserialized to vote state".to_string(),
1588 )
1589 })?;
1590
1591 Ok((vote_account, vote_state))
1592}
1593
1594pub async fn process_show_vote_account(
1595 rpc_client: &RpcClient,
1596 config: &CliConfig<'_>,
1597 vote_account_address: &Pubkey,
1598 use_lamports_unit: bool,
1599 use_csv: bool,
1600 with_rewards: Option<usize>,
1601 starting_epoch: Option<u64>,
1602) -> ProcessResult {
1603 let (vote_account, vote_state) =
1604 get_vote_account(rpc_client, vote_account_address, config.commitment).await?;
1605
1606 let epoch_schedule = rpc_client.get_epoch_schedule().await?;
1607 let tvc_activation_slot = rpc_client
1608 .get_account_with_commitment(
1609 &agave_feature_set::timely_vote_credits::id(),
1610 config.commitment,
1611 )
1612 .await
1613 .ok()
1614 .and_then(|response| response.value)
1615 .and_then(|account| from_account(&account))
1616 .and_then(|feature| feature.activated_at);
1617 let tvc_activation_epoch = tvc_activation_slot.map(|s| epoch_schedule.get_epoch(s));
1618
1619 let mut votes: Vec<CliLandedVote> = vec![];
1620 let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
1621 if !vote_state.votes.is_empty() {
1622 for vote in &vote_state.votes {
1623 votes.push(vote.into());
1624 }
1625 for (epoch, credits, prev_credits) in vote_state.epoch_credits.iter().copied() {
1626 let credits_earned = credits.saturating_sub(prev_credits);
1627 let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
1628 let is_tvc_active = tvc_activation_epoch.map(|e| epoch >= e).unwrap_or_default();
1629 let max_credits_per_slot = if is_tvc_active {
1630 VOTE_CREDITS_MAXIMUM_PER_SLOT
1631 } else {
1632 1
1633 };
1634 epoch_voting_history.push(CliEpochVotingHistory {
1635 epoch,
1636 slots_in_epoch,
1637 credits_earned,
1638 credits,
1639 prev_credits,
1640 max_credits_per_slot,
1641 });
1642 }
1643 }
1644
1645 let epoch_rewards = if let Some(num_epochs) = with_rewards {
1646 match crate::stake::fetch_epoch_rewards(
1647 rpc_client,
1648 vote_account_address,
1649 num_epochs,
1650 starting_epoch,
1651 )
1652 .await
1653 {
1654 Ok(rewards) => Some(rewards),
1655 Err(error) => {
1656 eprintln!("Failed to fetch epoch rewards: {error:?}");
1657 None
1658 }
1659 }
1660 } else {
1661 None
1662 };
1663
1664 let vote_account_data = CliVoteAccount {
1665 account_balance: vote_account.lamports,
1666 validator_identity: vote_state.node_pubkey.to_string(),
1667 authorized_voters: (&vote_state.authorized_voters).into(),
1668 authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
1669 credits: vote_state.credits(),
1670 commission: vote_state
1671 .inflation_rewards_commission_bps
1672 .div_ceil(100)
1673 .min(u8::MAX as u16) as u8,
1674 root_slot: vote_state.root_slot,
1675 recent_timestamp: vote_state.last_timestamp.clone(),
1676 votes,
1677 epoch_voting_history,
1678 use_lamports_unit,
1679 use_csv,
1680 epoch_rewards,
1681 inflation_rewards_commission_bps: vote_state.inflation_rewards_commission_bps,
1682 inflation_rewards_collector: vote_state.inflation_rewards_collector.to_string(),
1683 block_revenue_collector: vote_state.block_revenue_collector.to_string(),
1684 block_revenue_commission_bps: vote_state.block_revenue_commission_bps,
1685 pending_delegator_rewards: vote_state.pending_delegator_rewards,
1686 bls_pubkey_compressed: vote_state
1687 .bls_pubkey_compressed
1688 .map(|bytes| bs58::encode(bytes).into_string()),
1689 };
1690
1691 Ok(config.output_format.formatted_string(&vote_account_data))
1692}
1693
1694#[allow(clippy::too_many_arguments)]
1695pub async fn process_withdraw_from_vote_account(
1696 rpc_client: &RpcClient,
1697 config: &CliConfig<'_>,
1698 vote_account_pubkey: &Pubkey,
1699 withdraw_authority: SignerIndex,
1700 withdraw_amount: SpendAmount,
1701 destination_account_pubkey: &Pubkey,
1702 sign_only: bool,
1703 dump_transaction_message: bool,
1704 blockhash_query: &BlockhashQuery,
1705 nonce_account: Option<&Pubkey>,
1706 nonce_authority: SignerIndex,
1707 memo: Option<&String>,
1708 fee_payer: SignerIndex,
1709 compute_unit_price: Option<u64>,
1710) -> ProcessResult {
1711 let withdraw_authority = config.signers[withdraw_authority];
1712 let recent_blockhash = blockhash_query
1713 .get_blockhash(rpc_client, config.commitment)
1714 .await?;
1715
1716 let fee_payer = config.signers[fee_payer];
1717 let nonce_authority = config.signers[nonce_authority];
1718
1719 let compute_unit_limit = match blockhash_query {
1720 BlockhashQuery::Static(_) | BlockhashQuery::Validated(_, _) => ComputeUnitLimit::Default,
1721 BlockhashQuery::Rpc(_) => ComputeUnitLimit::Simulated,
1722 };
1723 let build_message = |lamports| {
1724 let ixs = vec![withdraw(
1725 vote_account_pubkey,
1726 &withdraw_authority.pubkey(),
1727 lamports,
1728 destination_account_pubkey,
1729 )]
1730 .with_memo(memo)
1731 .with_compute_unit_config(&ComputeUnitConfig {
1732 compute_unit_price,
1733 compute_unit_limit,
1734 });
1735
1736 if let Some(nonce_account) = &nonce_account {
1737 Message::new_with_nonce(
1738 ixs,
1739 Some(&fee_payer.pubkey()),
1740 nonce_account,
1741 &nonce_authority.pubkey(),
1742 )
1743 } else {
1744 Message::new(&ixs, Some(&fee_payer.pubkey()))
1745 }
1746 };
1747
1748 let (message, _) = resolve_spend_tx_and_check_account_balances(
1749 rpc_client,
1750 sign_only,
1751 withdraw_amount,
1752 &recent_blockhash,
1753 vote_account_pubkey,
1754 &fee_payer.pubkey(),
1755 compute_unit_limit,
1756 build_message,
1757 config.commitment,
1758 )
1759 .await?;
1760
1761 if !sign_only {
1762 let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1763 let minimum_balance = rpc_client
1764 .get_minimum_balance_for_rent_exemption(VoteStateV4::size_of())
1765 .await?;
1766 if let SpendAmount::Some(withdraw_amount) = withdraw_amount {
1767 let balance_remaining = current_balance.saturating_sub(withdraw_amount);
1768 if balance_remaining < minimum_balance && balance_remaining != 0 {
1769 return Err(CliError::BadParameter(format!(
1770 "Withdraw amount too large. The vote account balance must be at least {} SOL \
1771 to remain rent exempt",
1772 build_balance_message(minimum_balance, false, false)
1773 ))
1774 .into());
1775 }
1776 }
1777 }
1778
1779 let mut tx = Transaction::new_unsigned(message);
1780
1781 if sign_only {
1782 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1783 return_signers_with_config(
1784 &tx,
1785 &config.output_format,
1786 &ReturnSignersConfig {
1787 dump_transaction_message,
1788 },
1789 )
1790 } else {
1791 tx.try_sign(&config.signers, recent_blockhash)?;
1792 if let Some(nonce_account) = &nonce_account {
1793 let nonce_account =
1794 solana_rpc_client_nonce_utils::nonblocking::get_account_with_commitment(
1795 rpc_client,
1796 nonce_account,
1797 config.commitment,
1798 )
1799 .await?;
1800 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1801 }
1802 check_account_for_fee_with_commitment(
1803 rpc_client,
1804 &tx.message.account_keys[0],
1805 &tx.message,
1806 config.commitment,
1807 )
1808 .await?;
1809 let result = rpc_client
1810 .send_and_confirm_transaction_with_spinner_and_config(
1811 &tx,
1812 config.commitment,
1813 config.send_transaction_config,
1814 )
1815 .await;
1816 log_instruction_custom_error::<VoteError>(result, config)
1817 }
1818}
1819
1820pub async fn process_close_vote_account(
1821 rpc_client: &RpcClient,
1822 config: &CliConfig<'_>,
1823 vote_account_pubkey: &Pubkey,
1824 withdraw_authority: SignerIndex,
1825 destination_account_pubkey: &Pubkey,
1826 memo: Option<&String>,
1827 fee_payer: SignerIndex,
1828 compute_unit_price: Option<u64>,
1829) -> ProcessResult {
1830 let vote_account_status = rpc_client
1831 .get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1832 vote_pubkey: Some(vote_account_pubkey.to_string()),
1833 ..RpcGetVoteAccountsConfig::default()
1834 })
1835 .await?;
1836
1837 if let Some(vote_account) = vote_account_status
1838 .current
1839 .into_iter()
1840 .chain(vote_account_status.delinquent)
1841 .next()
1842 {
1843 if vote_account.activated_stake != 0 {
1844 return Err(format!(
1845 "Cannot close a vote account with active stake: {vote_account_pubkey}"
1846 )
1847 .into());
1848 }
1849 }
1850
1851 let latest_blockhash = rpc_client.get_latest_blockhash().await?;
1852 let withdraw_authority = config.signers[withdraw_authority];
1853 let fee_payer = config.signers[fee_payer];
1854
1855 let current_balance = rpc_client.get_balance(vote_account_pubkey).await?;
1856
1857 let compute_unit_limit = ComputeUnitLimit::Simulated;
1858 let ixs = vec![withdraw(
1859 vote_account_pubkey,
1860 &withdraw_authority.pubkey(),
1861 current_balance,
1862 destination_account_pubkey,
1863 )]
1864 .with_memo(memo)
1865 .with_compute_unit_config(&ComputeUnitConfig {
1866 compute_unit_price,
1867 compute_unit_limit,
1868 });
1869
1870 let mut message = Message::new(&ixs, Some(&fee_payer.pubkey()));
1871 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
1872 let mut tx = Transaction::new_unsigned(message);
1873 tx.try_sign(&config.signers, latest_blockhash)?;
1874 check_account_for_fee_with_commitment(
1875 rpc_client,
1876 &tx.message.account_keys[0],
1877 &tx.message,
1878 config.commitment,
1879 )
1880 .await?;
1881 let result = rpc_client
1882 .send_and_confirm_transaction_with_spinner_and_config(
1883 &tx,
1884 config.commitment,
1885 config.send_transaction_config,
1886 )
1887 .await;
1888 log_instruction_custom_error::<VoteError>(result, config)
1889}
1890
1891#[cfg(test)]
1892mod tests {
1893 use {
1894 super::*,
1895 crate::{clap_app::get_clap_app, cli::parse_command},
1896 solana_hash::Hash,
1897 solana_keypair::{Keypair, read_keypair_file, write_keypair},
1898 solana_presigner::Presigner,
1899 solana_rpc_client_nonce_utils::nonblocking::blockhash_query::Source,
1900 solana_signer::Signer,
1901 tempfile::NamedTempFile,
1902 };
1903
1904 fn make_tmp_file() -> (String, NamedTempFile) {
1905 let tmp_file = NamedTempFile::new().unwrap();
1906 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
1907 }
1908
1909 #[test]
1910 fn test_parse_command() {
1911 let test_commands = get_clap_app("test", "desc", "version");
1912 let keypair = Keypair::new();
1913 let pubkey = keypair.pubkey();
1914 let pubkey_string = pubkey.to_string();
1915 let keypair2 = Keypair::new();
1916 let pubkey2 = keypair2.pubkey();
1917 let pubkey2_string = pubkey2.to_string();
1918 let sig2 = keypair2.sign_message(&[0u8]);
1919 let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
1920
1921 let default_keypair = Keypair::new();
1922 let (default_keypair_file, mut tmp_file) = make_tmp_file();
1923 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
1924 let default_signer = DefaultSigner::new("", &default_keypair_file);
1925
1926 let blockhash = Hash::default();
1927 let blockhash_string = format!("{blockhash}");
1928 let nonce_account = Pubkey::new_unique();
1929
1930 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1932 "test",
1933 "vote-authorize-voter",
1934 &pubkey_string,
1935 &default_keypair_file,
1936 &pubkey2_string,
1937 ]);
1938 assert_eq!(
1939 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1940 CliCommandInfo {
1941 command: CliCommand::VoteAuthorize {
1942 vote_account_pubkey: pubkey,
1943 new_authorized_pubkey: pubkey2,
1944 vote_authorize: VoteAuthorize::Voter,
1945 use_v2_instruction: false,
1946 sign_only: false,
1947 dump_transaction_message: false,
1948 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1949 nonce_account: None,
1950 nonce_authority: 0,
1951 memo: None,
1952 fee_payer: 0,
1953 authorized: 0,
1954 new_authorized: None,
1955 compute_unit_price: None,
1956 },
1957 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
1958 }
1959 );
1960
1961 let authorized_keypair = Keypair::new();
1962 let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
1963 write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
1964
1965 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1966 "test",
1967 "vote-authorize-voter",
1968 &pubkey_string,
1969 &authorized_keypair_file,
1970 &pubkey2_string,
1971 ]);
1972 assert_eq!(
1973 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1974 CliCommandInfo {
1975 command: CliCommand::VoteAuthorize {
1976 vote_account_pubkey: pubkey,
1977 new_authorized_pubkey: pubkey2,
1978 vote_authorize: VoteAuthorize::Voter,
1979 use_v2_instruction: false,
1980 sign_only: false,
1981 dump_transaction_message: false,
1982 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
1983 nonce_account: None,
1984 nonce_authority: 0,
1985 memo: None,
1986 fee_payer: 0,
1987 authorized: 1,
1988 new_authorized: None,
1989 compute_unit_price: None,
1990 },
1991 signers: vec![
1992 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1993 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1994 ],
1995 }
1996 );
1997
1998 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1999 "test",
2000 "vote-authorize-voter",
2001 &pubkey_string,
2002 &authorized_keypair_file,
2003 &pubkey2_string,
2004 "--blockhash",
2005 &blockhash_string,
2006 "--sign-only",
2007 ]);
2008 assert_eq!(
2009 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2010 CliCommandInfo {
2011 command: CliCommand::VoteAuthorize {
2012 vote_account_pubkey: pubkey,
2013 new_authorized_pubkey: pubkey2,
2014 vote_authorize: VoteAuthorize::Voter,
2015 use_v2_instruction: false,
2016 sign_only: true,
2017 dump_transaction_message: false,
2018 blockhash_query: BlockhashQuery::Static(blockhash),
2019 nonce_account: None,
2020 nonce_authority: 0,
2021 memo: None,
2022 fee_payer: 0,
2023 authorized: 1,
2024 new_authorized: None,
2025 compute_unit_price: None,
2026 },
2027 signers: vec![
2028 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2029 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2030 ],
2031 }
2032 );
2033
2034 let authorized_sig = authorized_keypair.sign_message(&[0u8]);
2035 let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig);
2036 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2037 "test",
2038 "vote-authorize-voter",
2039 &pubkey_string,
2040 &authorized_keypair.pubkey().to_string(),
2041 &pubkey2_string,
2042 "--blockhash",
2043 &blockhash_string,
2044 "--signer",
2045 &authorized_signer,
2046 "--signer",
2047 &signer2,
2048 "--fee-payer",
2049 &pubkey2_string,
2050 "--nonce",
2051 &nonce_account.to_string(),
2052 "--nonce-authority",
2053 &pubkey2_string,
2054 ]);
2055 assert_eq!(
2056 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2057 CliCommandInfo {
2058 command: CliCommand::VoteAuthorize {
2059 vote_account_pubkey: pubkey,
2060 new_authorized_pubkey: pubkey2,
2061 vote_authorize: VoteAuthorize::Voter,
2062 use_v2_instruction: false,
2063 sign_only: false,
2064 dump_transaction_message: false,
2065 blockhash_query: BlockhashQuery::Validated(
2066 Source::NonceAccount(nonce_account),
2067 blockhash
2068 ),
2069 nonce_account: Some(nonce_account),
2070 nonce_authority: 0,
2071 memo: None,
2072 fee_payer: 0,
2073 authorized: 1,
2074 new_authorized: None,
2075 compute_unit_price: None,
2076 },
2077 signers: vec![
2078 Box::new(Presigner::new(&pubkey2, &sig2)),
2079 Box::new(Presigner::new(
2080 &authorized_keypair.pubkey(),
2081 &authorized_sig
2082 )),
2083 ],
2084 }
2085 );
2086
2087 let (voter_keypair_file, mut tmp_file) = make_tmp_file();
2089 let voter_keypair = Keypair::new();
2090 write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
2091
2092 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2093 "test",
2094 "vote-authorize-voter-checked",
2095 &pubkey_string,
2096 &default_keypair_file,
2097 &voter_keypair_file,
2098 ]);
2099 assert_eq!(
2100 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2101 CliCommandInfo {
2102 command: CliCommand::VoteAuthorize {
2103 vote_account_pubkey: pubkey,
2104 new_authorized_pubkey: voter_keypair.pubkey(),
2105 vote_authorize: VoteAuthorize::Voter,
2106 use_v2_instruction: false,
2107 sign_only: false,
2108 dump_transaction_message: false,
2109 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2110 nonce_account: None,
2111 nonce_authority: 0,
2112 memo: None,
2113 fee_payer: 0,
2114 authorized: 0,
2115 new_authorized: Some(1),
2116 compute_unit_price: None,
2117 },
2118 signers: vec![
2119 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2120 Box::new(read_keypair_file(&voter_keypair_file).unwrap())
2121 ],
2122 }
2123 );
2124
2125 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2126 "test",
2127 "vote-authorize-voter-checked",
2128 &pubkey_string,
2129 &authorized_keypair_file,
2130 &voter_keypair_file,
2131 ]);
2132 assert_eq!(
2133 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
2134 CliCommandInfo {
2135 command: CliCommand::VoteAuthorize {
2136 vote_account_pubkey: pubkey,
2137 new_authorized_pubkey: voter_keypair.pubkey(),
2138 vote_authorize: VoteAuthorize::Voter,
2139 use_v2_instruction: false,
2140 sign_only: false,
2141 dump_transaction_message: false,
2142 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2143 nonce_account: None,
2144 nonce_authority: 0,
2145 memo: None,
2146 fee_payer: 0,
2147 authorized: 1,
2148 new_authorized: Some(2),
2149 compute_unit_price: None,
2150 },
2151 signers: vec![
2152 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2153 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2154 Box::new(read_keypair_file(&voter_keypair_file).unwrap()),
2155 ],
2156 }
2157 );
2158
2159 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
2160 "test",
2161 "vote-authorize-voter-checked",
2162 &pubkey_string,
2163 &authorized_keypair_file,
2164 &pubkey2_string,
2165 ]);
2166 assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
2167
2168 let (new_voter_keypair_file, mut tmp_file) = make_tmp_file();
2170 let new_voter_keypair = Keypair::new();
2171 write_keypair(&new_voter_keypair, tmp_file.as_file_mut()).unwrap();
2172
2173 let test_authorize_voter_checked_with_bls = test_commands.clone().get_matches_from(vec![
2174 "test",
2175 "vote-authorize-voter-checked",
2176 &pubkey_string,
2177 &authorized_keypair_file,
2178 &new_voter_keypair_file,
2179 "--use-v2-instruction",
2180 ]);
2181 assert_eq!(
2182 parse_command(
2183 &test_authorize_voter_checked_with_bls,
2184 &default_signer,
2185 &mut None
2186 )
2187 .unwrap(),
2188 CliCommandInfo {
2189 command: CliCommand::VoteAuthorize {
2190 vote_account_pubkey: pubkey,
2191 new_authorized_pubkey: new_voter_keypair.pubkey(),
2192 vote_authorize: VoteAuthorize::Voter,
2193 use_v2_instruction: true,
2194 sign_only: false,
2195 dump_transaction_message: false,
2196 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2197 nonce_account: None,
2198 nonce_authority: 0,
2199 memo: None,
2200 fee_payer: 0,
2201 authorized: 1,
2202 new_authorized: Some(2),
2203 compute_unit_price: None,
2204 },
2205 signers: vec![
2206 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2207 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
2208 Box::new(read_keypair_file(&new_voter_keypair_file).unwrap()),
2209 ],
2210 }
2211 );
2212
2213 let (identity_keypair_file, mut tmp_file) = make_tmp_file();
2215 let identity_keypair = Keypair::new();
2216 let authorized_withdrawer = Keypair::new().pubkey();
2217 write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
2218 let (keypair_file, mut tmp_file) = make_tmp_file();
2219 let keypair = Keypair::new();
2220 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2221
2222 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2223 "test",
2224 "create-vote-account",
2225 &keypair_file,
2226 &identity_keypair_file,
2227 &authorized_withdrawer.to_string(),
2228 "--commission",
2229 "10",
2230 ]);
2231 assert_eq!(
2232 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2233 CliCommandInfo {
2234 command: CliCommand::CreateVoteAccount {
2235 vote_account: 1,
2236 seed: None,
2237 identity_account: 2,
2238 authorized_voter: None,
2239 authorized_withdrawer,
2240 commission: Some(10),
2241 use_v2_instruction: false,
2242
2243 inflation_rewards_commission_bps: None,
2244 inflation_rewards_collector: None,
2245 block_revenue_commission_bps: None,
2246 block_revenue_collector: None,
2247 sign_only: false,
2248 dump_transaction_message: false,
2249 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2250 nonce_account: None,
2251 nonce_authority: 0,
2252 memo: None,
2253 fee_payer: 0,
2254 compute_unit_price: None,
2255 },
2256 signers: vec![
2257 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2258 Box::new(read_keypair_file(&keypair_file).unwrap()),
2259 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2260 ],
2261 }
2262 );
2263
2264 let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
2265 "test",
2266 "create-vote-account",
2267 &keypair_file,
2268 &identity_keypair_file,
2269 &authorized_withdrawer.to_string(),
2270 ]);
2271 assert_eq!(
2272 parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
2273 CliCommandInfo {
2274 command: CliCommand::CreateVoteAccount {
2275 vote_account: 1,
2276 seed: None,
2277 identity_account: 2,
2278 authorized_voter: None,
2279 authorized_withdrawer,
2280 commission: None, use_v2_instruction: false,
2282
2283 inflation_rewards_commission_bps: None,
2284 inflation_rewards_collector: None,
2285 block_revenue_commission_bps: None,
2286 block_revenue_collector: None,
2287 sign_only: false,
2288 dump_transaction_message: false,
2289 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2290 nonce_account: None,
2291 nonce_authority: 0,
2292 memo: None,
2293 fee_payer: 0,
2294 compute_unit_price: None,
2295 },
2296 signers: vec![
2297 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2298 Box::new(read_keypair_file(&keypair_file).unwrap()),
2299 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2300 ],
2301 }
2302 );
2303
2304 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2305 "test",
2306 "create-vote-account",
2307 &keypair_file,
2308 &identity_keypair_file,
2309 &authorized_withdrawer.to_string(),
2310 "--commission",
2311 "10",
2312 "--blockhash",
2313 &blockhash_string,
2314 "--sign-only",
2315 "--fee-payer",
2316 &default_keypair.pubkey().to_string(),
2317 ]);
2318 assert_eq!(
2319 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2320 CliCommandInfo {
2321 command: CliCommand::CreateVoteAccount {
2322 vote_account: 1,
2323 seed: None,
2324 identity_account: 2,
2325 authorized_voter: None,
2326 authorized_withdrawer,
2327 commission: Some(10), use_v2_instruction: false,
2329
2330 inflation_rewards_commission_bps: None,
2331 inflation_rewards_collector: None,
2332 block_revenue_commission_bps: None,
2333 block_revenue_collector: None,
2334 sign_only: true,
2335 dump_transaction_message: false,
2336 blockhash_query: BlockhashQuery::Static(blockhash),
2337 nonce_account: None,
2338 nonce_authority: 0,
2339 memo: None,
2340 fee_payer: 0,
2341 compute_unit_price: None,
2342 },
2343 signers: vec![
2344 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2345 Box::new(read_keypair_file(&keypair_file).unwrap()),
2346 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2347 ],
2348 }
2349 );
2350
2351 let identity_sig = identity_keypair.sign_message(&[0u8]);
2352 let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig);
2353 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
2354 "test",
2355 "create-vote-account",
2356 &keypair_file,
2357 &identity_keypair.pubkey().to_string(),
2358 &authorized_withdrawer.to_string(),
2359 "--commission",
2360 "10",
2361 "--blockhash",
2362 &blockhash_string,
2363 "--signer",
2364 &identity_signer,
2365 "--signer",
2366 &signer2,
2367 "--fee-payer",
2368 &default_keypair_file,
2369 "--nonce",
2370 &nonce_account.to_string(),
2371 "--nonce-authority",
2372 &pubkey2_string,
2373 ]);
2374 assert_eq!(
2375 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
2376 CliCommandInfo {
2377 command: CliCommand::CreateVoteAccount {
2378 vote_account: 1,
2379 seed: None,
2380 identity_account: 2,
2381 authorized_voter: None,
2382 authorized_withdrawer,
2383 commission: Some(10),
2384 use_v2_instruction: false,
2385
2386 inflation_rewards_commission_bps: None,
2387 inflation_rewards_collector: None,
2388 block_revenue_commission_bps: None,
2389 block_revenue_collector: None,
2390 sign_only: false,
2391 dump_transaction_message: false,
2392 blockhash_query: BlockhashQuery::Validated(
2393 Source::NonceAccount(nonce_account),
2394 blockhash
2395 ),
2396 nonce_account: Some(nonce_account),
2397 nonce_authority: 3,
2398 memo: None,
2399 fee_payer: 0,
2400 compute_unit_price: None,
2401 },
2402 signers: vec![
2403 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2404 Box::new(read_keypair_file(&keypair_file).unwrap()),
2405 Box::new(Presigner::new(&identity_keypair.pubkey(), &identity_sig)),
2406 Box::new(Presigner::new(&pubkey2, &sig2)),
2407 ],
2408 }
2409 );
2410
2411 let authed = solana_pubkey::new_rand();
2413 let (keypair_file, mut tmp_file) = make_tmp_file();
2414 let keypair = Keypair::new();
2415 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2416
2417 let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
2418 "test",
2419 "create-vote-account",
2420 &keypair_file,
2421 &identity_keypair_file,
2422 &authorized_withdrawer.to_string(),
2423 "--authorized-voter",
2424 &authed.to_string(),
2425 ]);
2426 assert_eq!(
2427 parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
2428 CliCommandInfo {
2429 command: CliCommand::CreateVoteAccount {
2430 vote_account: 1,
2431 seed: None,
2432 identity_account: 2,
2433 authorized_voter: Some(authed),
2434 authorized_withdrawer,
2435 commission: None, use_v2_instruction: false,
2437
2438 inflation_rewards_commission_bps: None,
2439 inflation_rewards_collector: None,
2440 block_revenue_commission_bps: None,
2441 block_revenue_collector: None,
2442 sign_only: false,
2443 dump_transaction_message: false,
2444 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2445 nonce_account: None,
2446 nonce_authority: 0,
2447 memo: None,
2448 fee_payer: 0,
2449 compute_unit_price: None,
2450 },
2451 signers: vec![
2452 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2453 Box::new(keypair),
2454 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2455 ],
2456 }
2457 );
2458
2459 let (keypair_file, mut tmp_file) = make_tmp_file();
2460 let keypair = Keypair::new();
2461 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2462 let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
2464 "test",
2465 "create-vote-account",
2466 &keypair_file,
2467 &identity_keypair_file,
2468 &identity_keypair_file,
2469 "--allow-unsafe-authorized-withdrawer",
2470 ]);
2471 assert_eq!(
2472 parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
2473 CliCommandInfo {
2474 command: CliCommand::CreateVoteAccount {
2475 vote_account: 1,
2476 seed: None,
2477 identity_account: 2,
2478 authorized_voter: None,
2479 authorized_withdrawer: identity_keypair.pubkey(),
2480 commission: None, use_v2_instruction: false,
2482
2483 inflation_rewards_commission_bps: None,
2484 inflation_rewards_collector: None,
2485 block_revenue_commission_bps: None,
2486 block_revenue_collector: None,
2487 sign_only: false,
2488 dump_transaction_message: false,
2489 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2490 nonce_account: None,
2491 nonce_authority: 0,
2492 memo: None,
2493 fee_payer: 0,
2494 compute_unit_price: None,
2495 },
2496 signers: vec![
2497 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2498 Box::new(read_keypair_file(&keypair_file).unwrap()),
2499 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2500 ],
2501 }
2502 );
2503
2504 let (keypair_file, mut tmp_file) = make_tmp_file();
2506 let keypair = Keypair::new();
2507 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2508 let inflation_rewards_collector = Keypair::new().pubkey();
2509 let block_revenue_collector = Keypair::new().pubkey();
2510
2511 let test_create_vote_account_v2 = test_commands.clone().get_matches_from(vec![
2513 "test",
2514 "create-vote-account",
2515 &keypair_file,
2516 &identity_keypair_file,
2517 &authorized_withdrawer.to_string(),
2518 "--use-v2-instruction",
2519 "--authorized-voter",
2520 &authed.to_string(),
2521 "--inflation-rewards-commission-bps",
2522 "500",
2523 "--inflation-rewards-collector",
2524 &inflation_rewards_collector.to_string(),
2525 "--block-revenue-commission-bps",
2526 "1000",
2527 "--block-revenue-collector",
2528 &block_revenue_collector.to_string(),
2529 ]);
2530 assert_eq!(
2531 parse_command(&test_create_vote_account_v2, &default_signer, &mut None).unwrap(),
2532 CliCommandInfo {
2533 command: CliCommand::CreateVoteAccount {
2534 vote_account: 1,
2535 seed: None,
2536 identity_account: 2,
2537 authorized_voter: Some(authed),
2538 authorized_withdrawer,
2539 commission: None,
2540 use_v2_instruction: true,
2541 inflation_rewards_commission_bps: Some(500),
2542 inflation_rewards_collector: Some(inflation_rewards_collector),
2543 block_revenue_commission_bps: Some(1000),
2544 block_revenue_collector: Some(block_revenue_collector),
2545 sign_only: false,
2546 dump_transaction_message: false,
2547 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2548 nonce_account: None,
2549 nonce_authority: 0,
2550 memo: None,
2551 fee_payer: 0,
2552 compute_unit_price: None,
2553 },
2554 signers: vec![
2555 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2556 Box::new(read_keypair_file(&keypair_file).unwrap()),
2557 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2558 ],
2559 }
2560 );
2561
2562 let (keypair_file, mut tmp_file) = make_tmp_file();
2564 let sign_only_vote_keypair = Keypair::new();
2565 write_keypair(&sign_only_vote_keypair, tmp_file.as_file_mut()).unwrap();
2566
2567 let test_create_vote_account_v2_sign_only = test_commands.clone().get_matches_from(vec![
2568 "test",
2569 "create-vote-account",
2570 &keypair_file,
2571 &identity_keypair_file,
2572 &authorized_withdrawer.to_string(),
2573 "--use-v2-instruction",
2574 "--blockhash",
2575 &blockhash_string,
2576 "--sign-only",
2577 ]);
2578 assert_eq!(
2579 parse_command(
2580 &test_create_vote_account_v2_sign_only,
2581 &default_signer,
2582 &mut None
2583 )
2584 .unwrap(),
2585 CliCommandInfo {
2586 command: CliCommand::CreateVoteAccount {
2587 vote_account: 1,
2588 seed: None,
2589 identity_account: 2,
2590 authorized_voter: None,
2591 authorized_withdrawer,
2592 commission: None,
2593 use_v2_instruction: true,
2594
2595 inflation_rewards_commission_bps: None,
2596 inflation_rewards_collector: None,
2597 block_revenue_commission_bps: None,
2598 block_revenue_collector: None,
2599 sign_only: true,
2600 dump_transaction_message: false,
2601 blockhash_query: BlockhashQuery::Static(blockhash),
2602 nonce_account: None,
2603 nonce_authority: 0,
2604 memo: None,
2605 fee_payer: 0,
2606 compute_unit_price: None,
2607 },
2608 signers: vec![
2609 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2610 Box::new(read_keypair_file(&keypair_file).unwrap()),
2611 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2612 ],
2613 }
2614 );
2615
2616 let (keypair_file, mut tmp_file) = make_tmp_file();
2618 let conflict_vote_keypair = Keypair::new();
2619 write_keypair(&conflict_vote_keypair, tmp_file.as_file_mut()).unwrap();
2620
2621 let test_conflict = test_commands.clone().get_matches_from(vec![
2623 "test",
2624 "create-vote-account",
2625 &keypair_file,
2626 &identity_keypair_file,
2627 &authorized_withdrawer.to_string(),
2628 "--commission",
2629 "10",
2630 "--use-v2-instruction",
2631 ]);
2632 assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2633
2634 let test_conflict = test_commands.clone().get_matches_from(vec![
2636 "test",
2637 "create-vote-account",
2638 &keypair_file,
2639 &identity_keypair_file,
2640 &authorized_withdrawer.to_string(),
2641 "--commission",
2642 "10",
2643 "--inflation-rewards-commission-bps",
2644 "1000",
2645 ]);
2646 assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2647
2648 let test_conflict = test_commands.clone().get_matches_from(vec![
2650 "test",
2651 "create-vote-account",
2652 &keypair_file,
2653 &identity_keypair_file,
2654 &authorized_withdrawer.to_string(),
2655 "--commission",
2656 "10",
2657 "--block-revenue-commission-bps",
2658 "500",
2659 ]);
2660 assert!(parse_command(&test_conflict, &default_signer, &mut None).is_err());
2661
2662 let test_update_validator = test_commands.clone().get_matches_from(vec![
2663 "test",
2664 "vote-update-validator",
2665 &pubkey_string,
2666 &identity_keypair_file,
2667 &keypair_file,
2668 ]);
2669 assert_eq!(
2670 parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
2671 CliCommandInfo {
2672 command: CliCommand::VoteUpdateValidator {
2673 vote_account_pubkey: pubkey,
2674 new_identity_account: 2,
2675 withdraw_authority: 1,
2676 sign_only: false,
2677 dump_transaction_message: false,
2678 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2679 nonce_account: None,
2680 nonce_authority: 0,
2681 memo: None,
2682 fee_payer: 0,
2683 compute_unit_price: None,
2684 },
2685 signers: vec![
2686 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2687 Box::new(read_keypair_file(&keypair_file).unwrap()),
2688 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2689 ],
2690 }
2691 );
2692
2693 let test_update_commission = test_commands.clone().get_matches_from(vec![
2694 "test",
2695 "vote-update-commission",
2696 &pubkey_string,
2697 "42",
2698 &keypair_file,
2699 ]);
2700 assert_eq!(
2701 parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
2702 CliCommandInfo {
2703 command: CliCommand::VoteUpdateCommission {
2704 vote_account_pubkey: pubkey,
2705 commission: 42,
2706 withdraw_authority: 1,
2707 sign_only: false,
2708 dump_transaction_message: false,
2709 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2710 nonce_account: None,
2711 nonce_authority: 0,
2712 memo: None,
2713 fee_payer: 0,
2714 compute_unit_price: None,
2715 },
2716 signers: vec![
2717 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2718 Box::new(read_keypair_file(&keypair_file).unwrap()),
2719 ],
2720 }
2721 );
2722
2723 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2725 "test",
2726 "withdraw-from-vote-account",
2727 &keypair_file,
2728 &pubkey_string,
2729 "42",
2730 ]);
2731 assert_eq!(
2732 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2733 CliCommandInfo {
2734 command: CliCommand::WithdrawFromVoteAccount {
2735 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2736 destination_account_pubkey: pubkey,
2737 withdraw_authority: 0,
2738 withdraw_amount: SpendAmount::Some(42_000_000_000),
2739 sign_only: false,
2740 dump_transaction_message: false,
2741 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2742 nonce_account: None,
2743 nonce_authority: 0,
2744 memo: None,
2745 fee_payer: 0,
2746 compute_unit_price: None,
2747 },
2748 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2749 }
2750 );
2751
2752 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2754 "test",
2755 "withdraw-from-vote-account",
2756 &keypair_file,
2757 &pubkey_string,
2758 "ALL",
2759 ]);
2760 assert_eq!(
2761 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2762 CliCommandInfo {
2763 command: CliCommand::WithdrawFromVoteAccount {
2764 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2765 destination_account_pubkey: pubkey,
2766 withdraw_authority: 0,
2767 withdraw_amount: SpendAmount::RentExempt,
2768 sign_only: false,
2769 dump_transaction_message: false,
2770 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2771 nonce_account: None,
2772 nonce_authority: 0,
2773 memo: None,
2774 fee_payer: 0,
2775 compute_unit_price: None,
2776 },
2777 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2778 }
2779 );
2780
2781 let withdraw_authority = Keypair::new();
2783 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2784 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2785 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2786 "test",
2787 "withdraw-from-vote-account",
2788 &keypair_file,
2789 &pubkey_string,
2790 "42",
2791 "--authorized-withdrawer",
2792 &withdraw_authority_file,
2793 ]);
2794 assert_eq!(
2795 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2796 CliCommandInfo {
2797 command: CliCommand::WithdrawFromVoteAccount {
2798 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2799 destination_account_pubkey: pubkey,
2800 withdraw_authority: 1,
2801 withdraw_amount: SpendAmount::Some(42_000_000_000),
2802 sign_only: false,
2803 dump_transaction_message: false,
2804 blockhash_query: BlockhashQuery::Rpc(Source::Cluster),
2805 nonce_account: None,
2806 nonce_authority: 0,
2807 memo: None,
2808 fee_payer: 0,
2809 compute_unit_price: None,
2810 },
2811 signers: vec![
2812 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2813 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2814 ],
2815 }
2816 );
2817
2818 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2820 "test",
2821 "withdraw-from-vote-account",
2822 &keypair.pubkey().to_string(),
2823 &pubkey_string,
2824 "42",
2825 "--authorized-withdrawer",
2826 &withdraw_authority_file,
2827 "--blockhash",
2828 &blockhash_string,
2829 "--sign-only",
2830 "--fee-payer",
2831 &withdraw_authority_file,
2832 ]);
2833 assert_eq!(
2834 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2835 CliCommandInfo {
2836 command: CliCommand::WithdrawFromVoteAccount {
2837 vote_account_pubkey: keypair.pubkey(),
2838 destination_account_pubkey: pubkey,
2839 withdraw_authority: 0,
2840 withdraw_amount: SpendAmount::Some(42_000_000_000),
2841 sign_only: true,
2842 dump_transaction_message: false,
2843 blockhash_query: BlockhashQuery::Static(blockhash),
2844 nonce_account: None,
2845 nonce_authority: 0,
2846 memo: None,
2847 fee_payer: 0,
2848 compute_unit_price: None,
2849 },
2850 signers: vec![Box::new(
2851 read_keypair_file(&withdraw_authority_file).unwrap()
2852 )],
2853 }
2854 );
2855
2856 let authorized_sig = withdraw_authority.sign_message(&[0u8]);
2857 let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig);
2858 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2859 "test",
2860 "withdraw-from-vote-account",
2861 &keypair.pubkey().to_string(),
2862 &pubkey_string,
2863 "42",
2864 "--authorized-withdrawer",
2865 &withdraw_authority.pubkey().to_string(),
2866 "--blockhash",
2867 &blockhash_string,
2868 "--signer",
2869 &authorized_signer,
2870 "--fee-payer",
2871 &withdraw_authority.pubkey().to_string(),
2872 ]);
2873 assert_eq!(
2874 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2875 CliCommandInfo {
2876 command: CliCommand::WithdrawFromVoteAccount {
2877 vote_account_pubkey: keypair.pubkey(),
2878 destination_account_pubkey: pubkey,
2879 withdraw_authority: 0,
2880 withdraw_amount: SpendAmount::Some(42_000_000_000),
2881 sign_only: false,
2882 dump_transaction_message: false,
2883 blockhash_query: BlockhashQuery::Validated(Source::Cluster, blockhash),
2884 nonce_account: None,
2885 nonce_authority: 0,
2886 memo: None,
2887 fee_payer: 0,
2888 compute_unit_price: None,
2889 },
2890 signers: vec![Box::new(Presigner::new(
2891 &withdraw_authority.pubkey(),
2892 &authorized_sig
2893 )),],
2894 }
2895 );
2896
2897 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2899 "test",
2900 "close-vote-account",
2901 &keypair_file,
2902 &pubkey_string,
2903 ]);
2904 assert_eq!(
2905 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2906 CliCommandInfo {
2907 command: CliCommand::CloseVoteAccount {
2908 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2909 destination_account_pubkey: pubkey,
2910 withdraw_authority: 0,
2911 memo: None,
2912 fee_payer: 0,
2913 compute_unit_price: None,
2914 },
2915 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2916 }
2917 );
2918
2919 let withdraw_authority = Keypair::new();
2921 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2922 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2923 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2924 "test",
2925 "close-vote-account",
2926 &keypair_file,
2927 &pubkey_string,
2928 "--authorized-withdrawer",
2929 &withdraw_authority_file,
2930 ]);
2931 assert_eq!(
2932 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2933 CliCommandInfo {
2934 command: CliCommand::CloseVoteAccount {
2935 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2936 destination_account_pubkey: pubkey,
2937 withdraw_authority: 1,
2938 memo: None,
2939 fee_payer: 0,
2940 compute_unit_price: None,
2941 },
2942 signers: vec![
2943 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2944 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2945 ],
2946 }
2947 );
2948
2949 let withdraw_authority = Keypair::new();
2951 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2952 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2953 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2954 "test",
2955 "close-vote-account",
2956 &keypair_file,
2957 &pubkey_string,
2958 "--authorized-withdrawer",
2959 &withdraw_authority_file,
2960 "--with-compute-unit-price",
2961 "99",
2962 ]);
2963 assert_eq!(
2964 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2965 CliCommandInfo {
2966 command: CliCommand::CloseVoteAccount {
2967 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2968 destination_account_pubkey: pubkey,
2969 withdraw_authority: 1,
2970 memo: None,
2971 fee_payer: 0,
2972 compute_unit_price: Some(99),
2973 },
2974 signers: vec![
2975 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2976 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2977 ],
2978 }
2979 );
2980 }
2981}