1use {
2 crate::{
3 checks::{check_account_for_fee_with_commitment, check_unique_pubkeys},
4 cli::{
5 log_instruction_custom_error, CliCommand, CliCommandInfo, CliConfig, CliError,
6 ProcessResult,
7 },
8 compute_budget::{
9 simulate_and_update_compute_unit_limit, ComputeUnitConfig, WithComputeUnitConfig,
10 },
11 memo::WithMemo,
12 nonce::check_nonce_account,
13 spend_utils::{resolve_spend_tx_and_check_account_balances, SpendAmount},
14 stake::check_current_authority,
15 },
16 clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand},
17 solana_account::Account,
18 solana_clap_utils::{
19 compute_budget::{compute_unit_price_arg, ComputeUnitLimit, COMPUTE_UNIT_PRICE_ARG},
20 fee_payer::{fee_payer_arg, FEE_PAYER_ARG},
21 input_parsers::*,
22 input_validators::*,
23 keypair::{DefaultSigner, SignerIndex},
24 memo::{memo_arg, MEMO_ARG},
25 nonce::*,
26 offline::*,
27 },
28 solana_cli_output::{
29 display::build_balance_message, return_signers_with_config, CliEpochVotingHistory,
30 CliLandedVote, CliVoteAccount, ReturnSignersConfig,
31 },
32 solana_commitment_config::CommitmentConfig,
33 solana_message::Message,
34 solana_pubkey::Pubkey,
35 solana_remote_wallet::remote_wallet::RemoteWalletManager,
36 solana_rpc_client::rpc_client::RpcClient,
37 solana_rpc_client_api::config::RpcGetVoteAccountsConfig,
38 solana_rpc_client_nonce_utils::blockhash_query::BlockhashQuery,
39 solana_system_interface::error::SystemError,
40 solana_transaction::Transaction,
41 solana_vote_program::{
42 vote_error::VoteError,
43 vote_instruction::{self, withdraw, CreateVoteAccountConfig},
44 vote_state::{
45 VoteAuthorize, VoteInit, VoteStateV3, VoteStateVersions, VOTE_CREDITS_MAXIMUM_PER_SLOT,
46 },
47 },
48 std::rc::Rc,
49};
50
51pub trait VoteSubCommands {
52 fn vote_subcommands(self) -> Self;
53}
54
55impl VoteSubCommands for App<'_, '_> {
56 fn vote_subcommands(self) -> Self {
57 self.subcommand(
58 SubCommand::with_name("create-vote-account")
59 .about("Create a vote account")
60 .arg(
61 Arg::with_name("vote_account")
62 .index(1)
63 .value_name("ACCOUNT_KEYPAIR")
64 .takes_value(true)
65 .required(true)
66 .validator(is_valid_signer)
67 .help("Vote account keypair to create"),
68 )
69 .arg(
70 Arg::with_name("identity_account")
71 .index(2)
72 .value_name("IDENTITY_KEYPAIR")
73 .takes_value(true)
74 .required(true)
75 .validator(is_valid_signer)
76 .help("Keypair of validator that will vote with this account"),
77 )
78 .arg(pubkey!(
79 Arg::with_name("authorized_withdrawer")
80 .index(3)
81 .value_name("WITHDRAWER_PUBKEY")
82 .takes_value(true)
83 .required(true)
84 .long("authorized-withdrawer"),
85 "Authorized withdrawer."
86 ))
87 .arg(
88 Arg::with_name("commission")
89 .long("commission")
90 .value_name("PERCENTAGE")
91 .takes_value(true)
92 .default_value("100")
93 .help("The commission taken on reward redemption (0-100)"),
94 )
95 .arg(pubkey!(
96 Arg::with_name("authorized_voter")
97 .long("authorized-voter")
98 .value_name("VOTER_PUBKEY"),
99 "Authorized voter [default: validator identity pubkey]."
100 ))
101 .arg(
102 Arg::with_name("allow_unsafe_authorized_withdrawer")
103 .long("allow-unsafe-authorized-withdrawer")
104 .takes_value(false)
105 .help(
106 "Allow an authorized withdrawer pubkey to be identical to the \
107 validator identity account pubkey or vote account pubkey, which is \
108 normally an unsafe configuration and should be avoided.",
109 ),
110 )
111 .arg(
112 Arg::with_name("seed")
113 .long("seed")
114 .value_name("STRING")
115 .takes_value(true)
116 .help(
117 "Seed for address generation; if specified, the resulting account \
118 will be at a derived address of the VOTE ACCOUNT pubkey",
119 ),
120 )
121 .offline_args()
122 .nonce_args(false)
123 .arg(fee_payer_arg())
124 .arg(memo_arg())
125 .arg(compute_unit_price_arg()),
126 )
127 .subcommand(
128 SubCommand::with_name("vote-authorize-voter")
129 .about("Authorize a new vote signing keypair for the given vote account")
130 .arg(pubkey!(
131 Arg::with_name("vote_account_pubkey")
132 .index(1)
133 .value_name("VOTE_ACCOUNT_ADDRESS")
134 .required(true),
135 "Vote account in which to set the authorized voter."
136 ))
137 .arg(
138 Arg::with_name("authorized")
139 .index(2)
140 .value_name("AUTHORIZED_KEYPAIR")
141 .required(true)
142 .validator(is_valid_signer)
143 .help("Current authorized vote signer."),
144 )
145 .arg(pubkey!(
146 Arg::with_name("new_authorized_pubkey")
147 .index(3)
148 .value_name("NEW_AUTHORIZED_PUBKEY")
149 .required(true),
150 "New authorized vote signer."
151 ))
152 .offline_args()
153 .nonce_args(false)
154 .arg(fee_payer_arg())
155 .arg(memo_arg())
156 .arg(compute_unit_price_arg()),
157 )
158 .subcommand(
159 SubCommand::with_name("vote-authorize-withdrawer")
160 .about("Authorize a new withdraw signing keypair for the given vote account")
161 .arg(pubkey!(
162 Arg::with_name("vote_account_pubkey")
163 .index(1)
164 .value_name("VOTE_ACCOUNT_ADDRESS")
165 .required(true),
166 "Vote account in which to set the authorized withdrawer."
167 ))
168 .arg(
169 Arg::with_name("authorized")
170 .index(2)
171 .value_name("AUTHORIZED_KEYPAIR")
172 .required(true)
173 .validator(is_valid_signer)
174 .help("Current authorized withdrawer."),
175 )
176 .arg(pubkey!(
177 Arg::with_name("new_authorized_pubkey")
178 .index(3)
179 .value_name("AUTHORIZED_PUBKEY")
180 .required(true),
181 "New authorized withdrawer."
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-checked")
191 .about(
192 "Authorize a new vote signing keypair for the given vote account, checking \
193 the new authority as a signer",
194 )
195 .arg(pubkey!(
196 Arg::with_name("vote_account_pubkey")
197 .index(1)
198 .value_name("VOTE_ACCOUNT_ADDRESS")
199 .required(true),
200 "Vote account in which to set the authorized voter."
201 ))
202 .arg(
203 Arg::with_name("authorized")
204 .index(2)
205 .value_name("AUTHORIZED_KEYPAIR")
206 .required(true)
207 .validator(is_valid_signer)
208 .help("Current authorized vote signer."),
209 )
210 .arg(
211 Arg::with_name("new_authorized")
212 .index(3)
213 .value_name("NEW_AUTHORIZED_KEYPAIR")
214 .required(true)
215 .validator(is_valid_signer)
216 .help("New authorized vote signer."),
217 )
218 .offline_args()
219 .nonce_args(false)
220 .arg(fee_payer_arg())
221 .arg(memo_arg())
222 .arg(compute_unit_price_arg()),
223 )
224 .subcommand(
225 SubCommand::with_name("vote-authorize-withdrawer-checked")
226 .about(
227 "Authorize a new withdraw signing keypair for the given vote account, \
228 checking the new authority as a signer",
229 )
230 .arg(pubkey!(
231 Arg::with_name("vote_account_pubkey")
232 .index(1)
233 .value_name("VOTE_ACCOUNT_ADDRESS")
234 .required(true),
235 "Vote account in which to set the authorized withdrawer."
236 ))
237 .arg(
238 Arg::with_name("authorized")
239 .index(2)
240 .value_name("AUTHORIZED_KEYPAIR")
241 .required(true)
242 .validator(is_valid_signer)
243 .help("Current authorized withdrawer."),
244 )
245 .arg(
246 Arg::with_name("new_authorized")
247 .index(3)
248 .value_name("NEW_AUTHORIZED_KEYPAIR")
249 .required(true)
250 .validator(is_valid_signer)
251 .help("New authorized withdrawer."),
252 )
253 .offline_args()
254 .nonce_args(false)
255 .arg(fee_payer_arg())
256 .arg(memo_arg())
257 .arg(compute_unit_price_arg()),
258 )
259 .subcommand(
260 SubCommand::with_name("vote-update-validator")
261 .about("Update the vote account's validator identity")
262 .arg(pubkey!(
263 Arg::with_name("vote_account_pubkey")
264 .index(1)
265 .value_name("VOTE_ACCOUNT_ADDRESS")
266 .required(true),
267 "Vote account to update."
268 ))
269 .arg(
270 Arg::with_name("new_identity_account")
271 .index(2)
272 .value_name("IDENTITY_KEYPAIR")
273 .takes_value(true)
274 .required(true)
275 .validator(is_valid_signer)
276 .help("Keypair of new validator that will vote with this account"),
277 )
278 .arg(
279 Arg::with_name("authorized_withdrawer")
280 .index(3)
281 .value_name("AUTHORIZED_KEYPAIR")
282 .takes_value(true)
283 .required(true)
284 .validator(is_valid_signer)
285 .help("Authorized withdrawer keypair"),
286 )
287 .offline_args()
288 .nonce_args(false)
289 .arg(fee_payer_arg())
290 .arg(memo_arg())
291 .arg(compute_unit_price_arg()),
292 )
293 .subcommand(
294 SubCommand::with_name("vote-update-commission")
295 .about("Update the vote account's commission")
296 .arg(pubkey!(
297 Arg::with_name("vote_account_pubkey")
298 .index(1)
299 .value_name("VOTE_ACCOUNT_ADDRESS")
300 .required(true),
301 "Vote account to update."
302 ))
303 .arg(
304 Arg::with_name("commission")
305 .index(2)
306 .value_name("PERCENTAGE")
307 .takes_value(true)
308 .required(true)
309 .validator(is_valid_percentage)
310 .help("The new commission"),
311 )
312 .arg(
313 Arg::with_name("authorized_withdrawer")
314 .index(3)
315 .value_name("AUTHORIZED_KEYPAIR")
316 .takes_value(true)
317 .required(true)
318 .validator(is_valid_signer)
319 .help("Authorized withdrawer keypair"),
320 )
321 .offline_args()
322 .nonce_args(false)
323 .arg(fee_payer_arg())
324 .arg(memo_arg())
325 .arg(compute_unit_price_arg()),
326 )
327 .subcommand(
328 SubCommand::with_name("vote-account")
329 .about("Show the contents of a vote account")
330 .alias("show-vote-account")
331 .arg(pubkey!(
332 Arg::with_name("vote_account_pubkey")
333 .index(1)
334 .value_name("VOTE_ACCOUNT_ADDRESS")
335 .required(true),
336 "Vote account."
337 ))
338 .arg(
339 Arg::with_name("lamports")
340 .long("lamports")
341 .takes_value(false)
342 .help("Display balance in lamports instead of SOL"),
343 )
344 .arg(
345 Arg::with_name("with_rewards")
346 .long("with-rewards")
347 .takes_value(false)
348 .help("Display inflation rewards"),
349 )
350 .arg(
351 Arg::with_name("csv")
352 .long("csv")
353 .takes_value(false)
354 .help("Format rewards in a CSV table"),
355 )
356 .arg(
357 Arg::with_name("starting_epoch")
358 .long("starting-epoch")
359 .takes_value(true)
360 .value_name("NUM")
361 .requires("with_rewards")
362 .help("Start displaying from epoch NUM"),
363 )
364 .arg(
365 Arg::with_name("num_rewards_epochs")
366 .long("num-rewards-epochs")
367 .takes_value(true)
368 .value_name("NUM")
369 .validator(|s| is_within_range(s, 1..=50))
370 .default_value_if("with_rewards", None, "1")
371 .requires("with_rewards")
372 .help(
373 "Display rewards for NUM recent epochs, max 10 \
374 [default: latest epoch only]",
375 ),
376 ),
377 )
378 .subcommand(
379 SubCommand::with_name("withdraw-from-vote-account")
380 .about("Withdraw lamports from a vote account into a specified account")
381 .arg(pubkey!(
382 Arg::with_name("vote_account_pubkey")
383 .index(1)
384 .value_name("VOTE_ACCOUNT_ADDRESS")
385 .required(true),
386 "Vote account from which to withdraw."
387 ))
388 .arg(pubkey!(
389 Arg::with_name("destination_account_pubkey")
390 .index(2)
391 .value_name("RECIPIENT_ADDRESS")
392 .required(true),
393 "The recipient of withdrawn SOL."
394 ))
395 .arg(
396 Arg::with_name("amount")
397 .index(3)
398 .value_name("AMOUNT")
399 .takes_value(true)
400 .required(true)
401 .validator(is_amount_or_all)
402 .help(
403 "The amount to withdraw, in SOL; accepts keyword ALL, which for this \
404 command means account balance minus rent-exempt minimum",
405 ),
406 )
407 .arg(
408 Arg::with_name("authorized_withdrawer")
409 .long("authorized-withdrawer")
410 .value_name("AUTHORIZED_KEYPAIR")
411 .takes_value(true)
412 .validator(is_valid_signer)
413 .help("Authorized withdrawer [default: cli config keypair]"),
414 )
415 .offline_args()
416 .nonce_args(false)
417 .arg(fee_payer_arg())
418 .arg(memo_arg())
419 .arg(compute_unit_price_arg()),
420 )
421 .subcommand(
422 SubCommand::with_name("close-vote-account")
423 .about("Close a vote account and withdraw all funds remaining")
424 .arg(pubkey!(
425 Arg::with_name("vote_account_pubkey")
426 .index(1)
427 .value_name("VOTE_ACCOUNT_ADDRESS")
428 .required(true),
429 "Vote account to be closed."
430 ))
431 .arg(pubkey!(
432 Arg::with_name("destination_account_pubkey")
433 .index(2)
434 .value_name("RECIPIENT_ADDRESS")
435 .required(true),
436 "The recipient of all withdrawn SOL."
437 ))
438 .arg(
439 Arg::with_name("authorized_withdrawer")
440 .long("authorized-withdrawer")
441 .value_name("AUTHORIZED_KEYPAIR")
442 .takes_value(true)
443 .validator(is_valid_signer)
444 .help("Authorized withdrawer [default: cli config keypair]"),
445 )
446 .arg(fee_payer_arg())
447 .arg(memo_arg())
448 .arg(compute_unit_price_arg()),
449 )
450 }
451}
452
453pub fn parse_create_vote_account(
454 matches: &ArgMatches<'_>,
455 default_signer: &DefaultSigner,
456 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
457) -> Result<CliCommandInfo, CliError> {
458 let (vote_account, vote_account_pubkey) = signer_of(matches, "vote_account", wallet_manager)?;
459 let seed = matches.value_of("seed").map(|s| s.to_string());
460 let (identity_account, identity_pubkey) =
461 signer_of(matches, "identity_account", wallet_manager)?;
462 let commission = value_t_or_exit!(matches, "commission", u8);
463 let authorized_voter = pubkey_of_signer(matches, "authorized_voter", wallet_manager)?;
464 let authorized_withdrawer =
465 pubkey_of_signer(matches, "authorized_withdrawer", wallet_manager)?.unwrap();
466 let allow_unsafe = matches.is_present("allow_unsafe_authorized_withdrawer");
467 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
468 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
469 let blockhash_query = BlockhashQuery::new_from_matches(matches);
470 let nonce_account = pubkey_of_signer(matches, NONCE_ARG.name, wallet_manager)?;
471 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
472 let (nonce_authority, nonce_authority_pubkey) =
473 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
474 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
475 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
476
477 if !allow_unsafe {
478 if authorized_withdrawer == vote_account_pubkey.unwrap() {
479 return Err(CliError::BadParameter(
480 "Authorized withdrawer pubkey is identical to vote account pubkey, an unsafe \
481 configuration"
482 .to_owned(),
483 ));
484 }
485 if authorized_withdrawer == identity_pubkey.unwrap() {
486 return Err(CliError::BadParameter(
487 "Authorized withdrawer pubkey is identical to identity account pubkey, an unsafe \
488 configuration"
489 .to_owned(),
490 ));
491 }
492 }
493
494 let mut bulk_signers = vec![fee_payer, vote_account, identity_account];
495 if nonce_account.is_some() {
496 bulk_signers.push(nonce_authority);
497 }
498 let signer_info =
499 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
500
501 Ok(CliCommandInfo {
502 command: CliCommand::CreateVoteAccount {
503 vote_account: signer_info.index_of(vote_account_pubkey).unwrap(),
504 seed,
505 identity_account: signer_info.index_of(identity_pubkey).unwrap(),
506 authorized_voter,
507 authorized_withdrawer,
508 commission,
509 sign_only,
510 dump_transaction_message,
511 blockhash_query,
512 nonce_account,
513 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
514 memo,
515 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
516 compute_unit_price,
517 },
518 signers: signer_info.signers,
519 })
520}
521
522pub fn parse_vote_authorize(
523 matches: &ArgMatches<'_>,
524 default_signer: &DefaultSigner,
525 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
526 vote_authorize: VoteAuthorize,
527 checked: bool,
528) -> Result<CliCommandInfo, CliError> {
529 let vote_account_pubkey =
530 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
531 let (authorized, authorized_pubkey) = signer_of(matches, "authorized", wallet_manager)?;
532
533 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
534 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
535 let blockhash_query = BlockhashQuery::new_from_matches(matches);
536 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
537 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
538 let (nonce_authority, nonce_authority_pubkey) =
539 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
540 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
541 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
542
543 let mut bulk_signers = vec![fee_payer, authorized];
544
545 let new_authorized_pubkey = if checked {
546 let (new_authorized_signer, new_authorized_pubkey) =
547 signer_of(matches, "new_authorized", wallet_manager)?;
548 bulk_signers.push(new_authorized_signer);
549 new_authorized_pubkey.unwrap()
550 } else {
551 pubkey_of_signer(matches, "new_authorized_pubkey", wallet_manager)?.unwrap()
552 };
553 if nonce_account.is_some() {
554 bulk_signers.push(nonce_authority);
555 }
556 let signer_info =
557 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
558
559 Ok(CliCommandInfo {
560 command: CliCommand::VoteAuthorize {
561 vote_account_pubkey,
562 new_authorized_pubkey,
563 vote_authorize,
564 sign_only,
565 dump_transaction_message,
566 blockhash_query,
567 nonce_account,
568 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
569 memo,
570 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
571 authorized: signer_info.index_of(authorized_pubkey).unwrap(),
572 new_authorized: if checked {
573 signer_info.index_of(Some(new_authorized_pubkey))
574 } else {
575 None
576 },
577 compute_unit_price,
578 },
579 signers: signer_info.signers,
580 })
581}
582
583pub fn parse_vote_update_validator(
584 matches: &ArgMatches<'_>,
585 default_signer: &DefaultSigner,
586 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
587) -> Result<CliCommandInfo, CliError> {
588 let vote_account_pubkey =
589 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
590 let (new_identity_account, new_identity_pubkey) =
591 signer_of(matches, "new_identity_account", wallet_manager)?;
592 let (authorized_withdrawer, authorized_withdrawer_pubkey) =
593 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
594
595 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
596 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
597 let blockhash_query = BlockhashQuery::new_from_matches(matches);
598 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
599 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
600 let (nonce_authority, nonce_authority_pubkey) =
601 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
602 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
603 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
604
605 let mut bulk_signers = vec![fee_payer, authorized_withdrawer, new_identity_account];
606 if nonce_account.is_some() {
607 bulk_signers.push(nonce_authority);
608 }
609 let signer_info =
610 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
611
612 Ok(CliCommandInfo {
613 command: CliCommand::VoteUpdateValidator {
614 vote_account_pubkey,
615 new_identity_account: signer_info.index_of(new_identity_pubkey).unwrap(),
616 withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
617 sign_only,
618 dump_transaction_message,
619 blockhash_query,
620 nonce_account,
621 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
622 memo,
623 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
624 compute_unit_price,
625 },
626 signers: signer_info.signers,
627 })
628}
629
630pub fn parse_vote_update_commission(
631 matches: &ArgMatches<'_>,
632 default_signer: &DefaultSigner,
633 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
634) -> Result<CliCommandInfo, CliError> {
635 let vote_account_pubkey =
636 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
637 let (authorized_withdrawer, authorized_withdrawer_pubkey) =
638 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
639 let commission = value_t_or_exit!(matches, "commission", u8);
640
641 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
642 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
643 let blockhash_query = BlockhashQuery::new_from_matches(matches);
644 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
645 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
646 let (nonce_authority, nonce_authority_pubkey) =
647 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
648 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
649 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
650
651 let mut bulk_signers = vec![fee_payer, authorized_withdrawer];
652 if nonce_account.is_some() {
653 bulk_signers.push(nonce_authority);
654 }
655 let signer_info =
656 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
657
658 Ok(CliCommandInfo {
659 command: CliCommand::VoteUpdateCommission {
660 vote_account_pubkey,
661 commission,
662 withdraw_authority: signer_info.index_of(authorized_withdrawer_pubkey).unwrap(),
663 sign_only,
664 dump_transaction_message,
665 blockhash_query,
666 nonce_account,
667 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
668 memo,
669 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
670 compute_unit_price,
671 },
672 signers: signer_info.signers,
673 })
674}
675
676pub fn parse_vote_get_account_command(
677 matches: &ArgMatches<'_>,
678 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
679) -> Result<CliCommandInfo, CliError> {
680 let vote_account_pubkey =
681 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
682 let use_lamports_unit = matches.is_present("lamports");
683 let use_csv = matches.is_present("csv");
684 let with_rewards = if matches.is_present("with_rewards") {
685 Some(value_of(matches, "num_rewards_epochs").unwrap())
686 } else {
687 None
688 };
689 let starting_epoch = value_of(matches, "starting_epoch");
690 Ok(CliCommandInfo::without_signers(
691 CliCommand::ShowVoteAccount {
692 pubkey: vote_account_pubkey,
693 use_lamports_unit,
694 use_csv,
695 with_rewards,
696 starting_epoch,
697 },
698 ))
699}
700
701pub fn parse_withdraw_from_vote_account(
702 matches: &ArgMatches<'_>,
703 default_signer: &DefaultSigner,
704 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
705) -> Result<CliCommandInfo, CliError> {
706 let vote_account_pubkey =
707 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
708 let destination_account_pubkey =
709 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
710 let mut withdraw_amount = SpendAmount::new_from_matches(matches, "amount");
711 if withdraw_amount == SpendAmount::All {
715 withdraw_amount = SpendAmount::RentExempt;
716 }
717
718 let (withdraw_authority, withdraw_authority_pubkey) =
719 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
720
721 let sign_only = matches.is_present(SIGN_ONLY_ARG.name);
722 let dump_transaction_message = matches.is_present(DUMP_TRANSACTION_MESSAGE.name);
723 let blockhash_query = BlockhashQuery::new_from_matches(matches);
724 let nonce_account = pubkey_of(matches, NONCE_ARG.name);
725 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
726 let (nonce_authority, nonce_authority_pubkey) =
727 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
728 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
729 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
730
731 let mut bulk_signers = vec![fee_payer, withdraw_authority];
732 if nonce_account.is_some() {
733 bulk_signers.push(nonce_authority);
734 }
735 let signer_info =
736 default_signer.generate_unique_signers(bulk_signers, matches, wallet_manager)?;
737
738 Ok(CliCommandInfo {
739 command: CliCommand::WithdrawFromVoteAccount {
740 vote_account_pubkey,
741 destination_account_pubkey,
742 withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
743 withdraw_amount,
744 sign_only,
745 dump_transaction_message,
746 blockhash_query,
747 nonce_account,
748 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
749 memo,
750 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
751 compute_unit_price,
752 },
753 signers: signer_info.signers,
754 })
755}
756
757pub fn parse_close_vote_account(
758 matches: &ArgMatches<'_>,
759 default_signer: &DefaultSigner,
760 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
761) -> Result<CliCommandInfo, CliError> {
762 let vote_account_pubkey =
763 pubkey_of_signer(matches, "vote_account_pubkey", wallet_manager)?.unwrap();
764 let destination_account_pubkey =
765 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
766
767 let (withdraw_authority, withdraw_authority_pubkey) =
768 signer_of(matches, "authorized_withdrawer", wallet_manager)?;
769 let (fee_payer, fee_payer_pubkey) = signer_of(matches, FEE_PAYER_ARG.name, wallet_manager)?;
770
771 let signer_info = default_signer.generate_unique_signers(
772 vec![fee_payer, withdraw_authority],
773 matches,
774 wallet_manager,
775 )?;
776 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
777 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
778
779 Ok(CliCommandInfo {
780 command: CliCommand::CloseVoteAccount {
781 vote_account_pubkey,
782 destination_account_pubkey,
783 withdraw_authority: signer_info.index_of(withdraw_authority_pubkey).unwrap(),
784 memo,
785 fee_payer: signer_info.index_of(fee_payer_pubkey).unwrap(),
786 compute_unit_price,
787 },
788 signers: signer_info.signers,
789 })
790}
791
792#[allow(clippy::too_many_arguments)]
793pub fn process_create_vote_account(
794 rpc_client: &RpcClient,
795 config: &CliConfig,
796 vote_account: SignerIndex,
797 seed: &Option<String>,
798 identity_account: SignerIndex,
799 authorized_voter: &Option<Pubkey>,
800 authorized_withdrawer: Pubkey,
801 commission: u8,
802 sign_only: bool,
803 dump_transaction_message: bool,
804 blockhash_query: &BlockhashQuery,
805 nonce_account: Option<&Pubkey>,
806 nonce_authority: SignerIndex,
807 memo: Option<&String>,
808 fee_payer: SignerIndex,
809 compute_unit_price: Option<u64>,
810) -> ProcessResult {
811 let vote_account = config.signers[vote_account];
812 let vote_account_pubkey = vote_account.pubkey();
813 let vote_account_address = if let Some(seed) = seed {
814 Pubkey::create_with_seed(&vote_account_pubkey, seed, &solana_vote_program::id())?
815 } else {
816 vote_account_pubkey
817 };
818 check_unique_pubkeys(
819 (&config.signers[0].pubkey(), "cli keypair".to_string()),
820 (&vote_account_address, "vote_account".to_string()),
821 )?;
822
823 let identity_account = config.signers[identity_account];
824 let identity_pubkey = identity_account.pubkey();
825 check_unique_pubkeys(
826 (&vote_account_address, "vote_account".to_string()),
827 (&identity_pubkey, "identity_pubkey".to_string()),
828 )?;
829
830 let required_balance = rpc_client
831 .get_minimum_balance_for_rent_exemption(VoteStateV3::size_of())?
832 .max(1);
833 let amount = SpendAmount::Some(required_balance);
834
835 let fee_payer = config.signers[fee_payer];
836 let nonce_authority = config.signers[nonce_authority];
837 let space = VoteStateVersions::vote_state_size_of(true) as u64;
838
839 let compute_unit_limit = match blockhash_query {
840 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
841 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
842 };
843 let build_message = |lamports| {
844 let vote_init = VoteInit {
845 node_pubkey: identity_pubkey,
846 authorized_voter: authorized_voter.unwrap_or(identity_pubkey),
847 authorized_withdrawer,
848 commission,
849 };
850 let mut create_vote_account_config = CreateVoteAccountConfig {
851 space,
852 ..CreateVoteAccountConfig::default()
853 };
854 let to = if let Some(seed) = seed {
855 create_vote_account_config.with_seed = Some((&vote_account_pubkey, seed));
856 &vote_account_address
857 } else {
858 &vote_account_pubkey
859 };
860
861 let ixs = vote_instruction::create_account_with_config(
862 &config.signers[0].pubkey(),
863 to,
864 &vote_init,
865 lamports,
866 create_vote_account_config,
867 )
868 .with_memo(memo)
869 .with_compute_unit_config(&ComputeUnitConfig {
870 compute_unit_price,
871 compute_unit_limit,
872 });
873
874 if let Some(nonce_account) = &nonce_account {
875 Message::new_with_nonce(
876 ixs,
877 Some(&fee_payer.pubkey()),
878 nonce_account,
879 &nonce_authority.pubkey(),
880 )
881 } else {
882 Message::new(&ixs, Some(&fee_payer.pubkey()))
883 }
884 };
885
886 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
887
888 let (message, _) = resolve_spend_tx_and_check_account_balances(
889 rpc_client,
890 sign_only,
891 amount,
892 &recent_blockhash,
893 &config.signers[0].pubkey(),
894 &fee_payer.pubkey(),
895 compute_unit_limit,
896 build_message,
897 config.commitment,
898 )?;
899
900 if !sign_only {
901 if let Ok(response) =
902 rpc_client.get_account_with_commitment(&vote_account_address, config.commitment)
903 {
904 if let Some(vote_account) = response.value {
905 let err_msg = if vote_account.owner == solana_vote_program::id() {
906 format!("Vote account {vote_account_address} already exists")
907 } else {
908 format!(
909 "Account {vote_account_address} already exists and is not a vote account"
910 )
911 };
912 return Err(CliError::BadParameter(err_msg).into());
913 }
914 }
915
916 if let Some(nonce_account) = &nonce_account {
917 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
918 rpc_client,
919 nonce_account,
920 config.commitment,
921 )?;
922 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
923 }
924 }
925
926 let mut tx = Transaction::new_unsigned(message);
927 if sign_only {
928 tx.try_partial_sign(&config.signers, recent_blockhash)?;
929 return_signers_with_config(
930 &tx,
931 &config.output_format,
932 &ReturnSignersConfig {
933 dump_transaction_message,
934 },
935 )
936 } else {
937 tx.try_sign(&config.signers, recent_blockhash)?;
938 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
939 &tx,
940 config.commitment,
941 config.send_transaction_config,
942 );
943 log_instruction_custom_error::<SystemError>(result, config)
944 }
945}
946
947#[allow(clippy::too_many_arguments)]
948pub fn process_vote_authorize(
949 rpc_client: &RpcClient,
950 config: &CliConfig,
951 vote_account_pubkey: &Pubkey,
952 new_authorized_pubkey: &Pubkey,
953 vote_authorize: VoteAuthorize,
954 authorized: SignerIndex,
955 new_authorized: Option<SignerIndex>,
956 sign_only: bool,
957 dump_transaction_message: bool,
958 blockhash_query: &BlockhashQuery,
959 nonce_account: Option<Pubkey>,
960 nonce_authority: SignerIndex,
961 memo: Option<&String>,
962 fee_payer: SignerIndex,
963 compute_unit_price: Option<u64>,
964) -> ProcessResult {
965 let authorized = config.signers[authorized];
966 let new_authorized_signer = new_authorized.map(|index| config.signers[index]);
967
968 let vote_state = if !sign_only {
969 Some(get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?.1)
970 } else {
971 None
972 };
973 match vote_authorize {
974 VoteAuthorize::Voter => {
975 if let Some(vote_state) = vote_state {
976 let current_epoch = rpc_client.get_epoch_info()?.epoch;
977 let current_authorized_voter = vote_state
978 .authorized_voters()
979 .get_authorized_voter(current_epoch)
980 .ok_or_else(|| {
981 CliError::RpcRequestError(
982 "Invalid vote account state; no authorized voters found".to_string(),
983 )
984 })?;
985 check_current_authority(
986 &[current_authorized_voter, vote_state.authorized_withdrawer],
987 &authorized.pubkey(),
988 )?;
989 if let Some(signer) = new_authorized_signer {
990 if signer.is_interactive() {
991 return Err(CliError::BadParameter(format!(
992 "invalid new authorized vote signer {new_authorized_pubkey:?}. \
993 Interactive vote signers not supported"
994 ))
995 .into());
996 }
997 }
998 }
999 }
1000 VoteAuthorize::Withdrawer => {
1001 check_unique_pubkeys(
1002 (&authorized.pubkey(), "authorized_account".to_string()),
1003 (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
1004 )?;
1005 if let Some(vote_state) = vote_state {
1006 check_current_authority(&[vote_state.authorized_withdrawer], &authorized.pubkey())?
1007 }
1008 }
1009 }
1010
1011 let vote_ix = if new_authorized_signer.is_some() {
1012 vote_instruction::authorize_checked(
1013 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, vote_authorize, )
1018 } else {
1019 vote_instruction::authorize(
1020 vote_account_pubkey, &authorized.pubkey(), new_authorized_pubkey, vote_authorize, )
1025 };
1026
1027 let compute_unit_limit = match blockhash_query {
1028 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1029 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1030 };
1031 let ixs = vec![vote_ix]
1032 .with_memo(memo)
1033 .with_compute_unit_config(&ComputeUnitConfig {
1034 compute_unit_price,
1035 compute_unit_limit,
1036 });
1037
1038 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1039
1040 let nonce_authority = config.signers[nonce_authority];
1041 let fee_payer = config.signers[fee_payer];
1042
1043 let mut message = if let Some(nonce_account) = &nonce_account {
1044 Message::new_with_nonce(
1045 ixs,
1046 Some(&fee_payer.pubkey()),
1047 nonce_account,
1048 &nonce_authority.pubkey(),
1049 )
1050 } else {
1051 Message::new(&ixs, Some(&fee_payer.pubkey()))
1052 };
1053 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1054 let mut tx = Transaction::new_unsigned(message);
1055
1056 if sign_only {
1057 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1058 return_signers_with_config(
1059 &tx,
1060 &config.output_format,
1061 &ReturnSignersConfig {
1062 dump_transaction_message,
1063 },
1064 )
1065 } else {
1066 tx.try_sign(&config.signers, recent_blockhash)?;
1067 if let Some(nonce_account) = &nonce_account {
1068 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1069 rpc_client,
1070 nonce_account,
1071 config.commitment,
1072 )?;
1073 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1074 }
1075 check_account_for_fee_with_commitment(
1076 rpc_client,
1077 &config.signers[0].pubkey(),
1078 &tx.message,
1079 config.commitment,
1080 )?;
1081 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1082 &tx,
1083 config.commitment,
1084 config.send_transaction_config,
1085 );
1086 log_instruction_custom_error::<VoteError>(result, config)
1087 }
1088}
1089
1090#[allow(clippy::too_many_arguments)]
1091pub fn process_vote_update_validator(
1092 rpc_client: &RpcClient,
1093 config: &CliConfig,
1094 vote_account_pubkey: &Pubkey,
1095 new_identity_account: SignerIndex,
1096 withdraw_authority: SignerIndex,
1097 sign_only: bool,
1098 dump_transaction_message: bool,
1099 blockhash_query: &BlockhashQuery,
1100 nonce_account: Option<Pubkey>,
1101 nonce_authority: SignerIndex,
1102 memo: Option<&String>,
1103 fee_payer: SignerIndex,
1104 compute_unit_price: Option<u64>,
1105) -> ProcessResult {
1106 let authorized_withdrawer = config.signers[withdraw_authority];
1107 let new_identity_account = config.signers[new_identity_account];
1108 let new_identity_pubkey = new_identity_account.pubkey();
1109 check_unique_pubkeys(
1110 (vote_account_pubkey, "vote_account_pubkey".to_string()),
1111 (&new_identity_pubkey, "new_identity_account".to_string()),
1112 )?;
1113 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1114 let compute_unit_limit = match blockhash_query {
1115 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1116 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1117 };
1118 let ixs = vec![vote_instruction::update_validator_identity(
1119 vote_account_pubkey,
1120 &authorized_withdrawer.pubkey(),
1121 &new_identity_pubkey,
1122 )]
1123 .with_memo(memo)
1124 .with_compute_unit_config(&ComputeUnitConfig {
1125 compute_unit_price,
1126 compute_unit_limit,
1127 });
1128 let nonce_authority = config.signers[nonce_authority];
1129 let fee_payer = config.signers[fee_payer];
1130
1131 let mut message = if let Some(nonce_account) = &nonce_account {
1132 Message::new_with_nonce(
1133 ixs,
1134 Some(&fee_payer.pubkey()),
1135 nonce_account,
1136 &nonce_authority.pubkey(),
1137 )
1138 } else {
1139 Message::new(&ixs, Some(&fee_payer.pubkey()))
1140 };
1141 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1142 let mut tx = Transaction::new_unsigned(message);
1143
1144 if sign_only {
1145 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1146 return_signers_with_config(
1147 &tx,
1148 &config.output_format,
1149 &ReturnSignersConfig {
1150 dump_transaction_message,
1151 },
1152 )
1153 } else {
1154 tx.try_sign(&config.signers, recent_blockhash)?;
1155 if let Some(nonce_account) = &nonce_account {
1156 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1157 rpc_client,
1158 nonce_account,
1159 config.commitment,
1160 )?;
1161 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1162 }
1163 check_account_for_fee_with_commitment(
1164 rpc_client,
1165 &config.signers[0].pubkey(),
1166 &tx.message,
1167 config.commitment,
1168 )?;
1169 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1170 &tx,
1171 config.commitment,
1172 config.send_transaction_config,
1173 );
1174 log_instruction_custom_error::<VoteError>(result, config)
1175 }
1176}
1177
1178#[allow(clippy::too_many_arguments)]
1179pub fn process_vote_update_commission(
1180 rpc_client: &RpcClient,
1181 config: &CliConfig,
1182 vote_account_pubkey: &Pubkey,
1183 commission: u8,
1184 withdraw_authority: SignerIndex,
1185 sign_only: bool,
1186 dump_transaction_message: bool,
1187 blockhash_query: &BlockhashQuery,
1188 nonce_account: Option<Pubkey>,
1189 nonce_authority: SignerIndex,
1190 memo: Option<&String>,
1191 fee_payer: SignerIndex,
1192 compute_unit_price: Option<u64>,
1193) -> ProcessResult {
1194 let authorized_withdrawer = config.signers[withdraw_authority];
1195 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1196 let compute_unit_limit = match blockhash_query {
1197 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1198 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1199 };
1200 let ixs = vec![vote_instruction::update_commission(
1201 vote_account_pubkey,
1202 &authorized_withdrawer.pubkey(),
1203 commission,
1204 )]
1205 .with_memo(memo)
1206 .with_compute_unit_config(&ComputeUnitConfig {
1207 compute_unit_price,
1208 compute_unit_limit,
1209 });
1210 let nonce_authority = config.signers[nonce_authority];
1211 let fee_payer = config.signers[fee_payer];
1212
1213 let mut message = if let Some(nonce_account) = &nonce_account {
1214 Message::new_with_nonce(
1215 ixs,
1216 Some(&fee_payer.pubkey()),
1217 nonce_account,
1218 &nonce_authority.pubkey(),
1219 )
1220 } else {
1221 Message::new(&ixs, Some(&fee_payer.pubkey()))
1222 };
1223 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1224 let mut tx = Transaction::new_unsigned(message);
1225 if sign_only {
1226 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1227 return_signers_with_config(
1228 &tx,
1229 &config.output_format,
1230 &ReturnSignersConfig {
1231 dump_transaction_message,
1232 },
1233 )
1234 } else {
1235 tx.try_sign(&config.signers, recent_blockhash)?;
1236 if let Some(nonce_account) = &nonce_account {
1237 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1238 rpc_client,
1239 nonce_account,
1240 config.commitment,
1241 )?;
1242 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1243 }
1244 check_account_for_fee_with_commitment(
1245 rpc_client,
1246 &config.signers[0].pubkey(),
1247 &tx.message,
1248 config.commitment,
1249 )?;
1250 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1251 &tx,
1252 config.commitment,
1253 config.send_transaction_config,
1254 );
1255 log_instruction_custom_error::<VoteError>(result, config)
1256 }
1257}
1258
1259pub(crate) fn get_vote_account(
1260 rpc_client: &RpcClient,
1261 vote_account_pubkey: &Pubkey,
1262 commitment_config: CommitmentConfig,
1263) -> Result<(Account, VoteStateV3), Box<dyn std::error::Error>> {
1264 let vote_account = rpc_client
1265 .get_account_with_commitment(vote_account_pubkey, commitment_config)?
1266 .value
1267 .ok_or_else(|| {
1268 CliError::RpcRequestError(format!("{vote_account_pubkey:?} account does not exist"))
1269 })?;
1270
1271 if vote_account.owner != solana_vote_program::id() {
1272 return Err(CliError::RpcRequestError(format!(
1273 "{vote_account_pubkey:?} is not a vote account"
1274 ))
1275 .into());
1276 }
1277 let vote_state = VoteStateV3::deserialize(&vote_account.data).map_err(|_| {
1278 CliError::RpcRequestError(
1279 "Account data could not be deserialized to vote state".to_string(),
1280 )
1281 })?;
1282
1283 Ok((vote_account, vote_state))
1284}
1285
1286pub fn process_show_vote_account(
1287 rpc_client: &RpcClient,
1288 config: &CliConfig,
1289 vote_account_address: &Pubkey,
1290 use_lamports_unit: bool,
1291 use_csv: bool,
1292 with_rewards: Option<usize>,
1293 starting_epoch: Option<u64>,
1294) -> ProcessResult {
1295 let (vote_account, vote_state) =
1296 get_vote_account(rpc_client, vote_account_address, config.commitment)?;
1297
1298 let epoch_schedule = rpc_client.get_epoch_schedule()?;
1299 let tvc_activation_slot =
1300 rpc_client.get_feature_activation_slot(&agave_feature_set::timely_vote_credits::id())?;
1301 let tvc_activation_epoch = tvc_activation_slot.map(|s| epoch_schedule.get_epoch(s));
1302
1303 let mut votes: Vec<CliLandedVote> = vec![];
1304 let mut epoch_voting_history: Vec<CliEpochVotingHistory> = vec![];
1305 if !vote_state.votes.is_empty() {
1306 for vote in &vote_state.votes {
1307 votes.push(vote.into());
1308 }
1309 for (epoch, credits, prev_credits) in vote_state.epoch_credits().iter().copied() {
1310 let credits_earned = credits.saturating_sub(prev_credits);
1311 let slots_in_epoch = epoch_schedule.get_slots_in_epoch(epoch);
1312 let is_tvc_active = tvc_activation_epoch.map(|e| epoch >= e).unwrap_or_default();
1313 let max_credits_per_slot = if is_tvc_active {
1314 VOTE_CREDITS_MAXIMUM_PER_SLOT
1315 } else {
1316 1
1317 };
1318 epoch_voting_history.push(CliEpochVotingHistory {
1319 epoch,
1320 slots_in_epoch,
1321 credits_earned,
1322 credits,
1323 prev_credits,
1324 max_credits_per_slot,
1325 });
1326 }
1327 }
1328
1329 let epoch_rewards =
1330 with_rewards.and_then(|num_epochs| {
1331 match crate::stake::fetch_epoch_rewards(
1332 rpc_client,
1333 vote_account_address,
1334 num_epochs,
1335 starting_epoch,
1336 ) {
1337 Ok(rewards) => Some(rewards),
1338 Err(error) => {
1339 eprintln!("Failed to fetch epoch rewards: {error:?}");
1340 None
1341 }
1342 }
1343 });
1344
1345 let vote_account_data = CliVoteAccount {
1346 account_balance: vote_account.lamports,
1347 validator_identity: vote_state.node_pubkey.to_string(),
1348 authorized_voters: vote_state.authorized_voters().into(),
1349 authorized_withdrawer: vote_state.authorized_withdrawer.to_string(),
1350 credits: vote_state.credits(),
1351 commission: vote_state.commission,
1352 root_slot: vote_state.root_slot,
1353 recent_timestamp: vote_state.last_timestamp.clone(),
1354 votes,
1355 epoch_voting_history,
1356 use_lamports_unit,
1357 use_csv,
1358 epoch_rewards,
1359 };
1360
1361 Ok(config.output_format.formatted_string(&vote_account_data))
1362}
1363
1364#[allow(clippy::too_many_arguments)]
1365pub fn process_withdraw_from_vote_account(
1366 rpc_client: &RpcClient,
1367 config: &CliConfig,
1368 vote_account_pubkey: &Pubkey,
1369 withdraw_authority: SignerIndex,
1370 withdraw_amount: SpendAmount,
1371 destination_account_pubkey: &Pubkey,
1372 sign_only: bool,
1373 dump_transaction_message: bool,
1374 blockhash_query: &BlockhashQuery,
1375 nonce_account: Option<&Pubkey>,
1376 nonce_authority: SignerIndex,
1377 memo: Option<&String>,
1378 fee_payer: SignerIndex,
1379 compute_unit_price: Option<u64>,
1380) -> ProcessResult {
1381 let withdraw_authority = config.signers[withdraw_authority];
1382 let recent_blockhash = blockhash_query.get_blockhash(rpc_client, config.commitment)?;
1383
1384 let fee_payer = config.signers[fee_payer];
1385 let nonce_authority = config.signers[nonce_authority];
1386
1387 let compute_unit_limit = match blockhash_query {
1388 BlockhashQuery::None(_) | BlockhashQuery::FeeCalculator(_, _) => ComputeUnitLimit::Default,
1389 BlockhashQuery::All(_) => ComputeUnitLimit::Simulated,
1390 };
1391 let build_message = |lamports| {
1392 let ixs = vec![withdraw(
1393 vote_account_pubkey,
1394 &withdraw_authority.pubkey(),
1395 lamports,
1396 destination_account_pubkey,
1397 )]
1398 .with_memo(memo)
1399 .with_compute_unit_config(&ComputeUnitConfig {
1400 compute_unit_price,
1401 compute_unit_limit,
1402 });
1403
1404 if let Some(nonce_account) = &nonce_account {
1405 Message::new_with_nonce(
1406 ixs,
1407 Some(&fee_payer.pubkey()),
1408 nonce_account,
1409 &nonce_authority.pubkey(),
1410 )
1411 } else {
1412 Message::new(&ixs, Some(&fee_payer.pubkey()))
1413 }
1414 };
1415
1416 let (message, _) = resolve_spend_tx_and_check_account_balances(
1417 rpc_client,
1418 sign_only,
1419 withdraw_amount,
1420 &recent_blockhash,
1421 vote_account_pubkey,
1422 &fee_payer.pubkey(),
1423 compute_unit_limit,
1424 build_message,
1425 config.commitment,
1426 )?;
1427
1428 if !sign_only {
1429 let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
1430 let minimum_balance =
1431 rpc_client.get_minimum_balance_for_rent_exemption(VoteStateV3::size_of())?;
1432 if let SpendAmount::Some(withdraw_amount) = withdraw_amount {
1433 let balance_remaining = current_balance.saturating_sub(withdraw_amount);
1434 if balance_remaining < minimum_balance && balance_remaining != 0 {
1435 return Err(CliError::BadParameter(format!(
1436 "Withdraw amount too large. The vote account balance must be at least {} SOL \
1437 to remain rent exempt",
1438 build_balance_message(minimum_balance, false, false)
1439 ))
1440 .into());
1441 }
1442 }
1443 }
1444
1445 let mut tx = Transaction::new_unsigned(message);
1446
1447 if sign_only {
1448 tx.try_partial_sign(&config.signers, recent_blockhash)?;
1449 return_signers_with_config(
1450 &tx,
1451 &config.output_format,
1452 &ReturnSignersConfig {
1453 dump_transaction_message,
1454 },
1455 )
1456 } else {
1457 tx.try_sign(&config.signers, recent_blockhash)?;
1458 if let Some(nonce_account) = &nonce_account {
1459 let nonce_account = solana_rpc_client_nonce_utils::get_account_with_commitment(
1460 rpc_client,
1461 nonce_account,
1462 config.commitment,
1463 )?;
1464 check_nonce_account(&nonce_account, &nonce_authority.pubkey(), &recent_blockhash)?;
1465 }
1466 check_account_for_fee_with_commitment(
1467 rpc_client,
1468 &tx.message.account_keys[0],
1469 &tx.message,
1470 config.commitment,
1471 )?;
1472 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1473 &tx,
1474 config.commitment,
1475 config.send_transaction_config,
1476 );
1477 log_instruction_custom_error::<VoteError>(result, config)
1478 }
1479}
1480
1481pub fn process_close_vote_account(
1482 rpc_client: &RpcClient,
1483 config: &CliConfig,
1484 vote_account_pubkey: &Pubkey,
1485 withdraw_authority: SignerIndex,
1486 destination_account_pubkey: &Pubkey,
1487 memo: Option<&String>,
1488 fee_payer: SignerIndex,
1489 compute_unit_price: Option<u64>,
1490) -> ProcessResult {
1491 let vote_account_status =
1492 rpc_client.get_vote_accounts_with_config(RpcGetVoteAccountsConfig {
1493 vote_pubkey: Some(vote_account_pubkey.to_string()),
1494 ..RpcGetVoteAccountsConfig::default()
1495 })?;
1496
1497 if let Some(vote_account) = vote_account_status
1498 .current
1499 .into_iter()
1500 .chain(vote_account_status.delinquent)
1501 .next()
1502 {
1503 if vote_account.activated_stake != 0 {
1504 return Err(format!(
1505 "Cannot close a vote account with active stake: {vote_account_pubkey}"
1506 )
1507 .into());
1508 }
1509 }
1510
1511 let latest_blockhash = rpc_client.get_latest_blockhash()?;
1512 let withdraw_authority = config.signers[withdraw_authority];
1513 let fee_payer = config.signers[fee_payer];
1514
1515 let current_balance = rpc_client.get_balance(vote_account_pubkey)?;
1516
1517 let compute_unit_limit = ComputeUnitLimit::Simulated;
1518 let ixs = vec![withdraw(
1519 vote_account_pubkey,
1520 &withdraw_authority.pubkey(),
1521 current_balance,
1522 destination_account_pubkey,
1523 )]
1524 .with_memo(memo)
1525 .with_compute_unit_config(&ComputeUnitConfig {
1526 compute_unit_price,
1527 compute_unit_limit,
1528 });
1529
1530 let mut message = Message::new(&ixs, Some(&fee_payer.pubkey()));
1531 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
1532 let mut tx = Transaction::new_unsigned(message);
1533 tx.try_sign(&config.signers, latest_blockhash)?;
1534 check_account_for_fee_with_commitment(
1535 rpc_client,
1536 &tx.message.account_keys[0],
1537 &tx.message,
1538 config.commitment,
1539 )?;
1540 let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
1541 &tx,
1542 config.commitment,
1543 config.send_transaction_config,
1544 );
1545 log_instruction_custom_error::<VoteError>(result, config)
1546}
1547
1548#[cfg(test)]
1549mod tests {
1550 use {
1551 super::*,
1552 crate::{clap_app::get_clap_app, cli::parse_command},
1553 solana_hash::Hash,
1554 solana_keypair::{read_keypair_file, write_keypair, Keypair},
1555 solana_presigner::Presigner,
1556 solana_rpc_client_nonce_utils::blockhash_query,
1557 solana_signer::Signer,
1558 tempfile::NamedTempFile,
1559 };
1560
1561 fn make_tmp_file() -> (String, NamedTempFile) {
1562 let tmp_file = NamedTempFile::new().unwrap();
1563 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
1564 }
1565
1566 #[test]
1567 fn test_parse_command() {
1568 let test_commands = get_clap_app("test", "desc", "version");
1569 let keypair = Keypair::new();
1570 let pubkey = keypair.pubkey();
1571 let pubkey_string = pubkey.to_string();
1572 let keypair2 = Keypair::new();
1573 let pubkey2 = keypair2.pubkey();
1574 let pubkey2_string = pubkey2.to_string();
1575 let sig2 = keypair2.sign_message(&[0u8]);
1576 let signer2 = format!("{}={}", keypair2.pubkey(), sig2);
1577
1578 let default_keypair = Keypair::new();
1579 let (default_keypair_file, mut tmp_file) = make_tmp_file();
1580 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
1581 let default_signer = DefaultSigner::new("", &default_keypair_file);
1582
1583 let blockhash = Hash::default();
1584 let blockhash_string = format!("{blockhash}");
1585 let nonce_account = Pubkey::new_unique();
1586
1587 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1589 "test",
1590 "vote-authorize-voter",
1591 &pubkey_string,
1592 &default_keypair_file,
1593 &pubkey2_string,
1594 ]);
1595 assert_eq!(
1596 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1597 CliCommandInfo {
1598 command: CliCommand::VoteAuthorize {
1599 vote_account_pubkey: pubkey,
1600 new_authorized_pubkey: pubkey2,
1601 vote_authorize: VoteAuthorize::Voter,
1602 sign_only: false,
1603 dump_transaction_message: false,
1604 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1605 nonce_account: None,
1606 nonce_authority: 0,
1607 memo: None,
1608 fee_payer: 0,
1609 authorized: 0,
1610 new_authorized: None,
1611 compute_unit_price: None,
1612 },
1613 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
1614 }
1615 );
1616
1617 let authorized_keypair = Keypair::new();
1618 let (authorized_keypair_file, mut tmp_file) = make_tmp_file();
1619 write_keypair(&authorized_keypair, tmp_file.as_file_mut()).unwrap();
1620
1621 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1622 "test",
1623 "vote-authorize-voter",
1624 &pubkey_string,
1625 &authorized_keypair_file,
1626 &pubkey2_string,
1627 ]);
1628 assert_eq!(
1629 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1630 CliCommandInfo {
1631 command: CliCommand::VoteAuthorize {
1632 vote_account_pubkey: pubkey,
1633 new_authorized_pubkey: pubkey2,
1634 vote_authorize: VoteAuthorize::Voter,
1635 sign_only: false,
1636 dump_transaction_message: false,
1637 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1638 nonce_account: None,
1639 nonce_authority: 0,
1640 memo: None,
1641 fee_payer: 0,
1642 authorized: 1,
1643 new_authorized: None,
1644 compute_unit_price: None,
1645 },
1646 signers: vec![
1647 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1648 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1649 ],
1650 }
1651 );
1652
1653 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1654 "test",
1655 "vote-authorize-voter",
1656 &pubkey_string,
1657 &authorized_keypair_file,
1658 &pubkey2_string,
1659 "--blockhash",
1660 &blockhash_string,
1661 "--sign-only",
1662 ]);
1663 assert_eq!(
1664 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1665 CliCommandInfo {
1666 command: CliCommand::VoteAuthorize {
1667 vote_account_pubkey: pubkey,
1668 new_authorized_pubkey: pubkey2,
1669 vote_authorize: VoteAuthorize::Voter,
1670 sign_only: true,
1671 dump_transaction_message: false,
1672 blockhash_query: BlockhashQuery::None(blockhash),
1673 nonce_account: None,
1674 nonce_authority: 0,
1675 memo: None,
1676 fee_payer: 0,
1677 authorized: 1,
1678 new_authorized: None,
1679 compute_unit_price: None,
1680 },
1681 signers: vec![
1682 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1683 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1684 ],
1685 }
1686 );
1687
1688 let authorized_sig = authorized_keypair.sign_message(&[0u8]);
1689 let authorized_signer = format!("{}={}", authorized_keypair.pubkey(), authorized_sig);
1690 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1691 "test",
1692 "vote-authorize-voter",
1693 &pubkey_string,
1694 &authorized_keypair.pubkey().to_string(),
1695 &pubkey2_string,
1696 "--blockhash",
1697 &blockhash_string,
1698 "--signer",
1699 &authorized_signer,
1700 "--signer",
1701 &signer2,
1702 "--fee-payer",
1703 &pubkey2_string,
1704 "--nonce",
1705 &nonce_account.to_string(),
1706 "--nonce-authority",
1707 &pubkey2_string,
1708 ]);
1709 assert_eq!(
1710 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1711 CliCommandInfo {
1712 command: CliCommand::VoteAuthorize {
1713 vote_account_pubkey: pubkey,
1714 new_authorized_pubkey: pubkey2,
1715 vote_authorize: VoteAuthorize::Voter,
1716 sign_only: false,
1717 dump_transaction_message: false,
1718 blockhash_query: BlockhashQuery::FeeCalculator(
1719 blockhash_query::Source::NonceAccount(nonce_account),
1720 blockhash
1721 ),
1722 nonce_account: Some(nonce_account),
1723 nonce_authority: 0,
1724 memo: None,
1725 fee_payer: 0,
1726 authorized: 1,
1727 new_authorized: None,
1728 compute_unit_price: None,
1729 },
1730 signers: vec![
1731 Box::new(Presigner::new(&pubkey2, &sig2)),
1732 Box::new(Presigner::new(
1733 &authorized_keypair.pubkey(),
1734 &authorized_sig
1735 )),
1736 ],
1737 }
1738 );
1739
1740 let (voter_keypair_file, mut tmp_file) = make_tmp_file();
1742 let voter_keypair = Keypair::new();
1743 write_keypair(&voter_keypair, tmp_file.as_file_mut()).unwrap();
1744
1745 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1746 "test",
1747 "vote-authorize-voter-checked",
1748 &pubkey_string,
1749 &default_keypair_file,
1750 &voter_keypair_file,
1751 ]);
1752 assert_eq!(
1753 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1754 CliCommandInfo {
1755 command: CliCommand::VoteAuthorize {
1756 vote_account_pubkey: pubkey,
1757 new_authorized_pubkey: voter_keypair.pubkey(),
1758 vote_authorize: VoteAuthorize::Voter,
1759 sign_only: false,
1760 dump_transaction_message: false,
1761 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1762 nonce_account: None,
1763 nonce_authority: 0,
1764 memo: None,
1765 fee_payer: 0,
1766 authorized: 0,
1767 new_authorized: Some(1),
1768 compute_unit_price: None,
1769 },
1770 signers: vec![
1771 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1772 Box::new(read_keypair_file(&voter_keypair_file).unwrap())
1773 ],
1774 }
1775 );
1776
1777 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1778 "test",
1779 "vote-authorize-voter-checked",
1780 &pubkey_string,
1781 &authorized_keypair_file,
1782 &voter_keypair_file,
1783 ]);
1784 assert_eq!(
1785 parse_command(&test_authorize_voter, &default_signer, &mut None).unwrap(),
1786 CliCommandInfo {
1787 command: CliCommand::VoteAuthorize {
1788 vote_account_pubkey: pubkey,
1789 new_authorized_pubkey: voter_keypair.pubkey(),
1790 vote_authorize: VoteAuthorize::Voter,
1791 sign_only: false,
1792 dump_transaction_message: false,
1793 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1794 nonce_account: None,
1795 nonce_authority: 0,
1796 memo: None,
1797 fee_payer: 0,
1798 authorized: 1,
1799 new_authorized: Some(2),
1800 compute_unit_price: None,
1801 },
1802 signers: vec![
1803 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1804 Box::new(read_keypair_file(&authorized_keypair_file).unwrap()),
1805 Box::new(read_keypair_file(&voter_keypair_file).unwrap()),
1806 ],
1807 }
1808 );
1809
1810 let test_authorize_voter = test_commands.clone().get_matches_from(vec![
1811 "test",
1812 "vote-authorize-voter-checked",
1813 &pubkey_string,
1814 &authorized_keypair_file,
1815 &pubkey2_string,
1816 ]);
1817 assert!(parse_command(&test_authorize_voter, &default_signer, &mut None).is_err());
1818
1819 let (identity_keypair_file, mut tmp_file) = make_tmp_file();
1821 let identity_keypair = Keypair::new();
1822 let authorized_withdrawer = Keypair::new().pubkey();
1823 write_keypair(&identity_keypair, tmp_file.as_file_mut()).unwrap();
1824 let (keypair_file, mut tmp_file) = make_tmp_file();
1825 let keypair = Keypair::new();
1826 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
1827
1828 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1829 "test",
1830 "create-vote-account",
1831 &keypair_file,
1832 &identity_keypair_file,
1833 &authorized_withdrawer.to_string(),
1834 "--commission",
1835 "10",
1836 ]);
1837 assert_eq!(
1838 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1839 CliCommandInfo {
1840 command: CliCommand::CreateVoteAccount {
1841 vote_account: 1,
1842 seed: None,
1843 identity_account: 2,
1844 authorized_voter: None,
1845 authorized_withdrawer,
1846 commission: 10,
1847 sign_only: false,
1848 dump_transaction_message: false,
1849 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1850 nonce_account: None,
1851 nonce_authority: 0,
1852 memo: None,
1853 fee_payer: 0,
1854 compute_unit_price: None,
1855 },
1856 signers: vec![
1857 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1858 Box::new(read_keypair_file(&keypair_file).unwrap()),
1859 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1860 ],
1861 }
1862 );
1863
1864 let test_create_vote_account2 = test_commands.clone().get_matches_from(vec![
1865 "test",
1866 "create-vote-account",
1867 &keypair_file,
1868 &identity_keypair_file,
1869 &authorized_withdrawer.to_string(),
1870 ]);
1871 assert_eq!(
1872 parse_command(&test_create_vote_account2, &default_signer, &mut None).unwrap(),
1873 CliCommandInfo {
1874 command: CliCommand::CreateVoteAccount {
1875 vote_account: 1,
1876 seed: None,
1877 identity_account: 2,
1878 authorized_voter: None,
1879 authorized_withdrawer,
1880 commission: 100,
1881 sign_only: false,
1882 dump_transaction_message: false,
1883 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
1884 nonce_account: None,
1885 nonce_authority: 0,
1886 memo: None,
1887 fee_payer: 0,
1888 compute_unit_price: None,
1889 },
1890 signers: vec![
1891 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1892 Box::new(read_keypair_file(&keypair_file).unwrap()),
1893 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1894 ],
1895 }
1896 );
1897
1898 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1899 "test",
1900 "create-vote-account",
1901 &keypair_file,
1902 &identity_keypair_file,
1903 &authorized_withdrawer.to_string(),
1904 "--commission",
1905 "10",
1906 "--blockhash",
1907 &blockhash_string,
1908 "--sign-only",
1909 "--fee-payer",
1910 &default_keypair.pubkey().to_string(),
1911 ]);
1912 assert_eq!(
1913 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1914 CliCommandInfo {
1915 command: CliCommand::CreateVoteAccount {
1916 vote_account: 1,
1917 seed: None,
1918 identity_account: 2,
1919 authorized_voter: None,
1920 authorized_withdrawer,
1921 commission: 10,
1922 sign_only: true,
1923 dump_transaction_message: false,
1924 blockhash_query: BlockhashQuery::None(blockhash),
1925 nonce_account: None,
1926 nonce_authority: 0,
1927 memo: None,
1928 fee_payer: 0,
1929 compute_unit_price: None,
1930 },
1931 signers: vec![
1932 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1933 Box::new(read_keypair_file(&keypair_file).unwrap()),
1934 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
1935 ],
1936 }
1937 );
1938
1939 let identity_sig = identity_keypair.sign_message(&[0u8]);
1940 let identity_signer = format!("{}={}", identity_keypair.pubkey(), identity_sig);
1941 let test_create_vote_account = test_commands.clone().get_matches_from(vec![
1942 "test",
1943 "create-vote-account",
1944 &keypair_file,
1945 &identity_keypair.pubkey().to_string(),
1946 &authorized_withdrawer.to_string(),
1947 "--commission",
1948 "10",
1949 "--blockhash",
1950 &blockhash_string,
1951 "--signer",
1952 &identity_signer,
1953 "--signer",
1954 &signer2,
1955 "--fee-payer",
1956 &default_keypair_file,
1957 "--nonce",
1958 &nonce_account.to_string(),
1959 "--nonce-authority",
1960 &pubkey2_string,
1961 ]);
1962 assert_eq!(
1963 parse_command(&test_create_vote_account, &default_signer, &mut None).unwrap(),
1964 CliCommandInfo {
1965 command: CliCommand::CreateVoteAccount {
1966 vote_account: 1,
1967 seed: None,
1968 identity_account: 2,
1969 authorized_voter: None,
1970 authorized_withdrawer,
1971 commission: 10,
1972 sign_only: false,
1973 dump_transaction_message: false,
1974 blockhash_query: BlockhashQuery::FeeCalculator(
1975 blockhash_query::Source::NonceAccount(nonce_account),
1976 blockhash
1977 ),
1978 nonce_account: Some(nonce_account),
1979 nonce_authority: 3,
1980 memo: None,
1981 fee_payer: 0,
1982 compute_unit_price: None,
1983 },
1984 signers: vec![
1985 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1986 Box::new(read_keypair_file(&keypair_file).unwrap()),
1987 Box::new(Presigner::new(&identity_keypair.pubkey(), &identity_sig)),
1988 Box::new(Presigner::new(&pubkey2, &sig2)),
1989 ],
1990 }
1991 );
1992
1993 let authed = solana_pubkey::new_rand();
1995 let (keypair_file, mut tmp_file) = make_tmp_file();
1996 let keypair = Keypair::new();
1997 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
1998
1999 let test_create_vote_account3 = test_commands.clone().get_matches_from(vec![
2000 "test",
2001 "create-vote-account",
2002 &keypair_file,
2003 &identity_keypair_file,
2004 &authorized_withdrawer.to_string(),
2005 "--authorized-voter",
2006 &authed.to_string(),
2007 ]);
2008 assert_eq!(
2009 parse_command(&test_create_vote_account3, &default_signer, &mut None).unwrap(),
2010 CliCommandInfo {
2011 command: CliCommand::CreateVoteAccount {
2012 vote_account: 1,
2013 seed: None,
2014 identity_account: 2,
2015 authorized_voter: Some(authed),
2016 authorized_withdrawer,
2017 commission: 100,
2018 sign_only: false,
2019 dump_transaction_message: false,
2020 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2021 nonce_account: None,
2022 nonce_authority: 0,
2023 memo: None,
2024 fee_payer: 0,
2025 compute_unit_price: None,
2026 },
2027 signers: vec![
2028 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2029 Box::new(keypair),
2030 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2031 ],
2032 }
2033 );
2034
2035 let (keypair_file, mut tmp_file) = make_tmp_file();
2036 let keypair = Keypair::new();
2037 write_keypair(&keypair, tmp_file.as_file_mut()).unwrap();
2038 let test_create_vote_account4 = test_commands.clone().get_matches_from(vec![
2040 "test",
2041 "create-vote-account",
2042 &keypair_file,
2043 &identity_keypair_file,
2044 &identity_keypair_file,
2045 "--allow-unsafe-authorized-withdrawer",
2046 ]);
2047 assert_eq!(
2048 parse_command(&test_create_vote_account4, &default_signer, &mut None).unwrap(),
2049 CliCommandInfo {
2050 command: CliCommand::CreateVoteAccount {
2051 vote_account: 1,
2052 seed: None,
2053 identity_account: 2,
2054 authorized_voter: None,
2055 authorized_withdrawer: identity_keypair.pubkey(),
2056 commission: 100,
2057 sign_only: false,
2058 dump_transaction_message: false,
2059 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2060 nonce_account: None,
2061 nonce_authority: 0,
2062 memo: None,
2063 fee_payer: 0,
2064 compute_unit_price: None,
2065 },
2066 signers: vec![
2067 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2068 Box::new(read_keypair_file(&keypair_file).unwrap()),
2069 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2070 ],
2071 }
2072 );
2073
2074 let test_update_validator = test_commands.clone().get_matches_from(vec![
2075 "test",
2076 "vote-update-validator",
2077 &pubkey_string,
2078 &identity_keypair_file,
2079 &keypair_file,
2080 ]);
2081 assert_eq!(
2082 parse_command(&test_update_validator, &default_signer, &mut None).unwrap(),
2083 CliCommandInfo {
2084 command: CliCommand::VoteUpdateValidator {
2085 vote_account_pubkey: pubkey,
2086 new_identity_account: 2,
2087 withdraw_authority: 1,
2088 sign_only: false,
2089 dump_transaction_message: false,
2090 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2091 nonce_account: None,
2092 nonce_authority: 0,
2093 memo: None,
2094 fee_payer: 0,
2095 compute_unit_price: None,
2096 },
2097 signers: vec![
2098 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2099 Box::new(read_keypair_file(&keypair_file).unwrap()),
2100 Box::new(read_keypair_file(&identity_keypair_file).unwrap()),
2101 ],
2102 }
2103 );
2104
2105 let test_update_commission = test_commands.clone().get_matches_from(vec![
2106 "test",
2107 "vote-update-commission",
2108 &pubkey_string,
2109 "42",
2110 &keypair_file,
2111 ]);
2112 assert_eq!(
2113 parse_command(&test_update_commission, &default_signer, &mut None).unwrap(),
2114 CliCommandInfo {
2115 command: CliCommand::VoteUpdateCommission {
2116 vote_account_pubkey: pubkey,
2117 commission: 42,
2118 withdraw_authority: 1,
2119 sign_only: false,
2120 dump_transaction_message: false,
2121 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2122 nonce_account: None,
2123 nonce_authority: 0,
2124 memo: None,
2125 fee_payer: 0,
2126 compute_unit_price: None,
2127 },
2128 signers: vec![
2129 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2130 Box::new(read_keypair_file(&keypair_file).unwrap()),
2131 ],
2132 }
2133 );
2134
2135 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2137 "test",
2138 "withdraw-from-vote-account",
2139 &keypair_file,
2140 &pubkey_string,
2141 "42",
2142 ]);
2143 assert_eq!(
2144 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2145 CliCommandInfo {
2146 command: CliCommand::WithdrawFromVoteAccount {
2147 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2148 destination_account_pubkey: pubkey,
2149 withdraw_authority: 0,
2150 withdraw_amount: SpendAmount::Some(42_000_000_000),
2151 sign_only: false,
2152 dump_transaction_message: false,
2153 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2154 nonce_account: None,
2155 nonce_authority: 0,
2156 memo: None,
2157 fee_payer: 0,
2158 compute_unit_price: None,
2159 },
2160 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2161 }
2162 );
2163
2164 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2166 "test",
2167 "withdraw-from-vote-account",
2168 &keypair_file,
2169 &pubkey_string,
2170 "ALL",
2171 ]);
2172 assert_eq!(
2173 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2174 CliCommandInfo {
2175 command: CliCommand::WithdrawFromVoteAccount {
2176 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2177 destination_account_pubkey: pubkey,
2178 withdraw_authority: 0,
2179 withdraw_amount: SpendAmount::RentExempt,
2180 sign_only: false,
2181 dump_transaction_message: false,
2182 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2183 nonce_account: None,
2184 nonce_authority: 0,
2185 memo: None,
2186 fee_payer: 0,
2187 compute_unit_price: None,
2188 },
2189 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2190 }
2191 );
2192
2193 let withdraw_authority = Keypair::new();
2195 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2196 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2197 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2198 "test",
2199 "withdraw-from-vote-account",
2200 &keypair_file,
2201 &pubkey_string,
2202 "42",
2203 "--authorized-withdrawer",
2204 &withdraw_authority_file,
2205 ]);
2206 assert_eq!(
2207 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2208 CliCommandInfo {
2209 command: CliCommand::WithdrawFromVoteAccount {
2210 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2211 destination_account_pubkey: pubkey,
2212 withdraw_authority: 1,
2213 withdraw_amount: SpendAmount::Some(42_000_000_000),
2214 sign_only: false,
2215 dump_transaction_message: false,
2216 blockhash_query: BlockhashQuery::All(blockhash_query::Source::Cluster),
2217 nonce_account: None,
2218 nonce_authority: 0,
2219 memo: None,
2220 fee_payer: 0,
2221 compute_unit_price: None,
2222 },
2223 signers: vec![
2224 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2225 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2226 ],
2227 }
2228 );
2229
2230 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2232 "test",
2233 "withdraw-from-vote-account",
2234 &keypair.pubkey().to_string(),
2235 &pubkey_string,
2236 "42",
2237 "--authorized-withdrawer",
2238 &withdraw_authority_file,
2239 "--blockhash",
2240 &blockhash_string,
2241 "--sign-only",
2242 "--fee-payer",
2243 &withdraw_authority_file,
2244 ]);
2245 assert_eq!(
2246 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2247 CliCommandInfo {
2248 command: CliCommand::WithdrawFromVoteAccount {
2249 vote_account_pubkey: keypair.pubkey(),
2250 destination_account_pubkey: pubkey,
2251 withdraw_authority: 0,
2252 withdraw_amount: SpendAmount::Some(42_000_000_000),
2253 sign_only: true,
2254 dump_transaction_message: false,
2255 blockhash_query: BlockhashQuery::None(blockhash),
2256 nonce_account: None,
2257 nonce_authority: 0,
2258 memo: None,
2259 fee_payer: 0,
2260 compute_unit_price: None,
2261 },
2262 signers: vec![Box::new(
2263 read_keypair_file(&withdraw_authority_file).unwrap()
2264 )],
2265 }
2266 );
2267
2268 let authorized_sig = withdraw_authority.sign_message(&[0u8]);
2269 let authorized_signer = format!("{}={}", withdraw_authority.pubkey(), authorized_sig);
2270 let test_withdraw_from_vote_account = test_commands.clone().get_matches_from(vec![
2271 "test",
2272 "withdraw-from-vote-account",
2273 &keypair.pubkey().to_string(),
2274 &pubkey_string,
2275 "42",
2276 "--authorized-withdrawer",
2277 &withdraw_authority.pubkey().to_string(),
2278 "--blockhash",
2279 &blockhash_string,
2280 "--signer",
2281 &authorized_signer,
2282 "--fee-payer",
2283 &withdraw_authority.pubkey().to_string(),
2284 ]);
2285 assert_eq!(
2286 parse_command(&test_withdraw_from_vote_account, &default_signer, &mut None).unwrap(),
2287 CliCommandInfo {
2288 command: CliCommand::WithdrawFromVoteAccount {
2289 vote_account_pubkey: keypair.pubkey(),
2290 destination_account_pubkey: pubkey,
2291 withdraw_authority: 0,
2292 withdraw_amount: SpendAmount::Some(42_000_000_000),
2293 sign_only: false,
2294 dump_transaction_message: false,
2295 blockhash_query: BlockhashQuery::FeeCalculator(
2296 blockhash_query::Source::Cluster,
2297 blockhash
2298 ),
2299 nonce_account: None,
2300 nonce_authority: 0,
2301 memo: None,
2302 fee_payer: 0,
2303 compute_unit_price: None,
2304 },
2305 signers: vec![Box::new(Presigner::new(
2306 &withdraw_authority.pubkey(),
2307 &authorized_sig
2308 )),],
2309 }
2310 );
2311
2312 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2314 "test",
2315 "close-vote-account",
2316 &keypair_file,
2317 &pubkey_string,
2318 ]);
2319 assert_eq!(
2320 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2321 CliCommandInfo {
2322 command: CliCommand::CloseVoteAccount {
2323 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2324 destination_account_pubkey: pubkey,
2325 withdraw_authority: 0,
2326 memo: None,
2327 fee_payer: 0,
2328 compute_unit_price: None,
2329 },
2330 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
2331 }
2332 );
2333
2334 let withdraw_authority = Keypair::new();
2336 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2337 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2338 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2339 "test",
2340 "close-vote-account",
2341 &keypair_file,
2342 &pubkey_string,
2343 "--authorized-withdrawer",
2344 &withdraw_authority_file,
2345 ]);
2346 assert_eq!(
2347 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2348 CliCommandInfo {
2349 command: CliCommand::CloseVoteAccount {
2350 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2351 destination_account_pubkey: pubkey,
2352 withdraw_authority: 1,
2353 memo: None,
2354 fee_payer: 0,
2355 compute_unit_price: None,
2356 },
2357 signers: vec![
2358 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2359 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2360 ],
2361 }
2362 );
2363
2364 let withdraw_authority = Keypair::new();
2366 let (withdraw_authority_file, mut tmp_file) = make_tmp_file();
2367 write_keypair(&withdraw_authority, tmp_file.as_file_mut()).unwrap();
2368 let test_close_vote_account = test_commands.clone().get_matches_from(vec![
2369 "test",
2370 "close-vote-account",
2371 &keypair_file,
2372 &pubkey_string,
2373 "--authorized-withdrawer",
2374 &withdraw_authority_file,
2375 "--with-compute-unit-price",
2376 "99",
2377 ]);
2378 assert_eq!(
2379 parse_command(&test_close_vote_account, &default_signer, &mut None).unwrap(),
2380 CliCommandInfo {
2381 command: CliCommand::CloseVoteAccount {
2382 vote_account_pubkey: read_keypair_file(&keypair_file).unwrap().pubkey(),
2383 destination_account_pubkey: pubkey,
2384 withdraw_authority: 1,
2385 memo: None,
2386 fee_payer: 0,
2387 compute_unit_price: Some(99),
2388 },
2389 signers: vec![
2390 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
2391 Box::new(read_keypair_file(&withdraw_authority_file).unwrap())
2392 ],
2393 }
2394 );
2395 }
2396}