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 memo::WithMemo,
12 spend_utils::{SpendAmount, resolve_spend_tx_and_check_account_balance},
13 },
14 clap::{App, Arg, ArgMatches, SubCommand},
15 solana_account::Account,
16 solana_clap_utils::{
17 compute_budget::{COMPUTE_UNIT_PRICE_ARG, ComputeUnitLimit, compute_unit_price_arg},
18 input_parsers::*,
19 input_validators::*,
20 keypair::{CliSigners, DefaultSigner, SignerIndex},
21 memo::{MEMO_ARG, memo_arg},
22 nonce::*,
23 },
24 solana_cli_output::CliNonceAccount,
25 solana_hash::Hash,
26 solana_message::Message,
27 solana_nonce::state::State,
28 solana_pubkey::Pubkey,
29 solana_remote_wallet::remote_wallet::RemoteWalletManager,
30 solana_rpc_client::nonblocking::rpc_client::RpcClient,
31 solana_rpc_client_nonce_utils::nonblocking::*,
32 solana_sdk_ids::system_program,
33 solana_system_interface::{
34 error::SystemError,
35 instruction::{
36 advance_nonce_account, authorize_nonce_account, create_nonce_account,
37 create_nonce_account_with_seed, upgrade_nonce_account, withdraw_nonce_account,
38 },
39 },
40 solana_transaction::Transaction,
41 std::rc::Rc,
42};
43
44pub trait NonceSubCommands {
45 fn nonce_subcommands(self) -> Self;
46}
47
48impl NonceSubCommands for App<'_, '_> {
49 fn nonce_subcommands(self) -> Self {
50 self.subcommand(
51 SubCommand::with_name("authorize-nonce-account")
52 .about("Assign account authority to a new entity")
53 .arg(pubkey!(
54 Arg::with_name("nonce_account_pubkey")
55 .index(1)
56 .value_name("NONCE_ACCOUNT_ADDRESS")
57 .required(true),
58 "Nonce account."
59 ))
60 .arg(pubkey!(
61 Arg::with_name("new_authority")
62 .index(2)
63 .value_name("AUTHORITY_PUBKEY")
64 .required(true),
65 "Account to be granted authority of the nonce account."
66 ))
67 .arg(nonce_authority_arg())
68 .arg(memo_arg())
69 .arg(compute_unit_price_arg()),
70 )
71 .subcommand(
72 SubCommand::with_name("create-nonce-account")
73 .about("Create a nonce account")
74 .arg(
75 Arg::with_name("nonce_account_keypair")
76 .index(1)
77 .value_name("ACCOUNT_KEYPAIR")
78 .takes_value(true)
79 .required(true)
80 .validator(is_valid_signer)
81 .help("Keypair of the nonce account to fund"),
82 )
83 .arg(
84 Arg::with_name("amount")
85 .index(2)
86 .value_name("AMOUNT")
87 .takes_value(true)
88 .required(true)
89 .validator(is_amount_or_all)
90 .help(
91 "The amount to load the nonce account with, in SOL; accepts keyword \
92 ALL",
93 ),
94 )
95 .arg(pubkey!(
96 Arg::with_name(NONCE_AUTHORITY_ARG.name)
97 .long(NONCE_AUTHORITY_ARG.long)
98 .value_name("PUBKEY"),
99 "Assign noncing authority to this other entity."
100 ))
101 .arg(
102 Arg::with_name("seed")
103 .long("seed")
104 .value_name("STRING")
105 .takes_value(true)
106 .help(
107 "Seed for address generation; if specified, the resulting account \
108 will be at a derived address of the NONCE_ACCOUNT pubkey",
109 ),
110 )
111 .arg(memo_arg())
112 .arg(compute_unit_price_arg()),
113 )
114 .subcommand(
115 SubCommand::with_name("nonce")
116 .about("Get the current nonce value")
117 .alias("get-nonce")
118 .arg(pubkey!(
119 Arg::with_name("nonce_account_pubkey")
120 .index(1)
121 .value_name("NONCE_ACCOUNT_ADDRESS")
122 .required(true),
123 "Nonce account to display."
124 )),
125 )
126 .subcommand(
127 SubCommand::with_name("new-nonce")
128 .about("Generate a new nonce, rendering the existing nonce useless")
129 .arg(pubkey!(
130 Arg::with_name("nonce_account_pubkey")
131 .index(1)
132 .value_name("NONCE_ACCOUNT_ADDRESS")
133 .required(true),
134 "Nonce account."
135 ))
136 .arg(nonce_authority_arg())
137 .arg(memo_arg())
138 .arg(compute_unit_price_arg()),
139 )
140 .subcommand(
141 SubCommand::with_name("nonce-account")
142 .about("Show the contents of a nonce account")
143 .alias("show-nonce-account")
144 .arg(pubkey!(
145 Arg::with_name("nonce_account_pubkey")
146 .index(1)
147 .value_name("NONCE_ACCOUNT_ADDRESS")
148 .required(true),
149 "Nonce account to display."
150 ))
151 .arg(
152 Arg::with_name("lamports")
153 .long("lamports")
154 .takes_value(false)
155 .help("Display balance in lamports instead of SOL"),
156 ),
157 )
158 .subcommand(
159 SubCommand::with_name("withdraw-from-nonce-account")
160 .about("Withdraw SOL from the nonce account")
161 .arg(pubkey!(
162 Arg::with_name("nonce_account_pubkey")
163 .index(1)
164 .value_name("NONCE_ACCOUNT_ADDRESS")
165 .required(true),
166 "Nonce account to withdraw from."
167 ))
168 .arg(pubkey!(
169 Arg::with_name("destination_account_pubkey")
170 .index(2)
171 .value_name("RECIPIENT_ADDRESS")
172 .required(true),
173 "Recipient of withdrawn SOL."
174 ))
175 .arg(
176 Arg::with_name("amount")
177 .index(3)
178 .value_name("AMOUNT")
179 .takes_value(true)
180 .required(true)
181 .validator(is_amount)
182 .help("The amount to withdraw from the nonce account, in SOL"),
183 )
184 .arg(nonce_authority_arg())
185 .arg(memo_arg())
186 .arg(compute_unit_price_arg()),
187 )
188 .subcommand(
189 SubCommand::with_name("upgrade-nonce-account")
190 .about(
191 "One-time idempotent upgrade of legacy nonce versions in order to bump them \
192 out of chain blockhash domain.",
193 )
194 .arg(pubkey!(
195 Arg::with_name("nonce_account_pubkey")
196 .index(1)
197 .value_name("NONCE_ACCOUNT_ADDRESS")
198 .required(true),
199 "Nonce account to upgrade."
200 ))
201 .arg(memo_arg())
202 .arg(compute_unit_price_arg()),
203 )
204 }
205}
206
207pub fn parse_authorize_nonce_account(
208 matches: &ArgMatches<'_>,
209 default_signer: &DefaultSigner,
210 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
211) -> Result<CliCommandInfo, CliError> {
212 let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
213 let new_authority = pubkey_of_signer(matches, "new_authority", wallet_manager)?.unwrap();
214 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
215 let (nonce_authority, nonce_authority_pubkey) =
216 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
217
218 let payer_provided = None;
219 let signer_info = default_signer.generate_unique_signers(
220 vec![payer_provided, nonce_authority],
221 matches,
222 wallet_manager,
223 )?;
224 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
225
226 Ok(CliCommandInfo {
227 command: CliCommand::AuthorizeNonceAccount {
228 nonce_account,
229 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
230 memo,
231 new_authority,
232 compute_unit_price,
233 },
234 signers: signer_info.signers,
235 })
236}
237
238pub fn parse_nonce_create_account(
239 matches: &ArgMatches<'_>,
240 default_signer: &DefaultSigner,
241 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
242) -> Result<CliCommandInfo, CliError> {
243 let (nonce_account, nonce_account_pubkey) =
244 signer_of(matches, "nonce_account_keypair", wallet_manager)?;
245 let seed = matches.value_of("seed").map(|s| s.to_string());
246 let amount = SpendAmount::new_from_matches(matches, "amount");
247 let nonce_authority = pubkey_of_signer(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
248 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
249
250 let payer_provided = None;
251 let signer_info = default_signer.generate_unique_signers(
252 vec![payer_provided, nonce_account],
253 matches,
254 wallet_manager,
255 )?;
256 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
257
258 Ok(CliCommandInfo {
259 command: CliCommand::CreateNonceAccount {
260 nonce_account: signer_info.index_of(nonce_account_pubkey).unwrap(),
261 seed,
262 nonce_authority,
263 memo,
264 amount,
265 compute_unit_price,
266 },
267 signers: signer_info.signers,
268 })
269}
270
271pub fn parse_get_nonce(
272 matches: &ArgMatches<'_>,
273 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
274) -> Result<CliCommandInfo, CliError> {
275 let nonce_account_pubkey =
276 pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
277
278 Ok(CliCommandInfo::without_signers(CliCommand::GetNonce(
279 nonce_account_pubkey,
280 )))
281}
282
283pub fn parse_new_nonce(
284 matches: &ArgMatches<'_>,
285 default_signer: &DefaultSigner,
286 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
287) -> Result<CliCommandInfo, CliError> {
288 let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
289 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
290 let (nonce_authority, nonce_authority_pubkey) =
291 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
292
293 let payer_provided = None;
294 let signer_info = default_signer.generate_unique_signers(
295 vec![payer_provided, nonce_authority],
296 matches,
297 wallet_manager,
298 )?;
299 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
300
301 Ok(CliCommandInfo {
302 command: CliCommand::NewNonce {
303 nonce_account,
304 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
305 memo,
306 compute_unit_price,
307 },
308 signers: signer_info.signers,
309 })
310}
311
312pub fn parse_show_nonce_account(
313 matches: &ArgMatches<'_>,
314 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
315) -> Result<CliCommandInfo, CliError> {
316 let nonce_account_pubkey =
317 pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
318 let use_lamports_unit = matches.is_present("lamports");
319
320 Ok(CliCommandInfo::without_signers(
321 CliCommand::ShowNonceAccount {
322 nonce_account_pubkey,
323 use_lamports_unit,
324 },
325 ))
326}
327
328pub fn parse_withdraw_from_nonce_account(
329 matches: &ArgMatches<'_>,
330 default_signer: &DefaultSigner,
331 wallet_manager: &mut Option<Rc<RemoteWalletManager>>,
332) -> Result<CliCommandInfo, CliError> {
333 let nonce_account = pubkey_of_signer(matches, "nonce_account_pubkey", wallet_manager)?.unwrap();
334 let destination_account_pubkey =
335 pubkey_of_signer(matches, "destination_account_pubkey", wallet_manager)?.unwrap();
336 let lamports = lamports_of_sol(matches, "amount").unwrap();
337 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
338 let (nonce_authority, nonce_authority_pubkey) =
339 signer_of(matches, NONCE_AUTHORITY_ARG.name, wallet_manager)?;
340
341 let payer_provided = None;
342 let signer_info = default_signer.generate_unique_signers(
343 vec![payer_provided, nonce_authority],
344 matches,
345 wallet_manager,
346 )?;
347 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
348
349 Ok(CliCommandInfo {
350 command: CliCommand::WithdrawFromNonceAccount {
351 nonce_account,
352 nonce_authority: signer_info.index_of(nonce_authority_pubkey).unwrap(),
353 memo,
354 destination_account_pubkey,
355 lamports,
356 compute_unit_price,
357 },
358 signers: signer_info.signers,
359 })
360}
361
362pub(crate) fn parse_upgrade_nonce_account(
363 matches: &ArgMatches<'_>,
364) -> Result<CliCommandInfo, CliError> {
365 let nonce_account = pubkey_of(matches, "nonce_account_pubkey").unwrap();
366 let memo = matches.value_of(MEMO_ARG.name).map(String::from);
367 let compute_unit_price = value_of(matches, COMPUTE_UNIT_PRICE_ARG.name);
368 Ok(CliCommandInfo {
369 command: CliCommand::UpgradeNonceAccount {
370 nonce_account,
371 memo,
372 compute_unit_price,
373 },
374 signers: CliSigners::default(),
375 })
376}
377
378pub fn check_nonce_account(
380 nonce_account: &Account,
381 nonce_authority: &Pubkey,
382 nonce_hash: &Hash,
383) -> Result<(), CliError> {
384 match state_from_account(nonce_account)? {
385 State::Initialized(ref data) => {
386 if &data.blockhash() != nonce_hash {
387 Err(Error::InvalidHash {
388 provided: *nonce_hash,
389 expected: data.blockhash(),
390 }
391 .into())
392 } else if nonce_authority != &data.authority {
393 Err(Error::InvalidAuthority {
394 provided: *nonce_authority,
395 expected: data.authority,
396 }
397 .into())
398 } else {
399 Ok(())
400 }
401 }
402 State::Uninitialized => Err(Error::InvalidStateForOperation.into()),
403 }
404}
405
406pub async fn process_authorize_nonce_account(
407 rpc_client: &RpcClient,
408 config: &CliConfig<'_>,
409 nonce_account: &Pubkey,
410 nonce_authority: SignerIndex,
411 memo: Option<&String>,
412 new_authority: &Pubkey,
413 compute_unit_price: Option<u64>,
414) -> ProcessResult {
415 let latest_blockhash = rpc_client.get_latest_blockhash().await?;
416
417 let nonce_authority = config.signers[nonce_authority];
418 let compute_unit_limit = ComputeUnitLimit::Simulated;
419 let ixs = vec![authorize_nonce_account(
420 nonce_account,
421 &nonce_authority.pubkey(),
422 new_authority,
423 )]
424 .with_memo(memo)
425 .with_compute_unit_config(&ComputeUnitConfig {
426 compute_unit_price,
427 compute_unit_limit,
428 });
429 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
430 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
431 let mut tx = Transaction::new_unsigned(message);
432 tx.try_sign(&config.signers, latest_blockhash)?;
433
434 check_account_for_fee_with_commitment(
435 rpc_client,
436 &config.signers[0].pubkey(),
437 &tx.message,
438 config.commitment,
439 )
440 .await?;
441 let result = rpc_client
442 .send_and_confirm_transaction_with_spinner_and_config(
443 &tx,
444 config.commitment,
445 config.send_transaction_config,
446 )
447 .await;
448
449 log_instruction_custom_error::<SystemError>(result, config)
450}
451
452pub async fn process_create_nonce_account(
453 rpc_client: &RpcClient,
454 config: &CliConfig<'_>,
455 nonce_account: SignerIndex,
456 seed: Option<String>,
457 nonce_authority: Option<Pubkey>,
458 memo: Option<&String>,
459 mut amount: SpendAmount,
460 compute_unit_price: Option<u64>,
461) -> ProcessResult {
462 let nonce_account_pubkey = config.signers[nonce_account].pubkey();
463 let nonce_account_address = if let Some(ref seed) = seed {
464 Pubkey::create_with_seed(&nonce_account_pubkey, seed, &system_program::id())?
465 } else {
466 nonce_account_pubkey
467 };
468
469 check_unique_pubkeys(
470 (&config.signers[0].pubkey(), "cli keypair".to_string()),
471 (&nonce_account_address, "nonce_account".to_string()),
472 )?;
473
474 let minimum_balance = rpc_client
475 .get_minimum_balance_for_rent_exemption(State::size())
476 .await?;
477 if amount == SpendAmount::All {
478 amount = SpendAmount::AllForAccountCreation {
479 create_account_min_balance: minimum_balance,
480 };
481 }
482
483 let nonce_authority = nonce_authority.unwrap_or_else(|| config.signers[0].pubkey());
484
485 let compute_unit_limit = ComputeUnitLimit::Simulated;
486 let build_message = |lamports| {
487 let ixs = if let Some(seed) = seed.clone() {
488 create_nonce_account_with_seed(
489 &config.signers[0].pubkey(), &nonce_account_address, &nonce_account_pubkey, &seed, &nonce_authority,
494 lamports,
495 )
496 .with_memo(memo)
497 .with_compute_unit_config(&ComputeUnitConfig {
498 compute_unit_price,
499 compute_unit_limit,
500 })
501 } else {
502 create_nonce_account(
503 &config.signers[0].pubkey(),
504 &nonce_account_pubkey,
505 &nonce_authority,
506 lamports,
507 )
508 .with_memo(memo)
509 .with_compute_unit_config(&ComputeUnitConfig {
510 compute_unit_price,
511 compute_unit_limit,
512 })
513 };
514 Message::new(&ixs, Some(&config.signers[0].pubkey()))
515 };
516
517 let latest_blockhash = rpc_client.get_latest_blockhash().await?;
518
519 let (message, lamports) = resolve_spend_tx_and_check_account_balance(
520 rpc_client,
521 false,
522 amount,
523 &latest_blockhash,
524 &config.signers[0].pubkey(),
525 compute_unit_limit,
526 build_message,
527 config.commitment,
528 )
529 .await?;
530
531 if let Ok(nonce_account) = get_account(rpc_client, &nonce_account_address).await {
532 let err_msg = if state_from_account(&nonce_account).is_ok() {
533 format!("Nonce account {nonce_account_address} already exists")
534 } else {
535 format!("Account {nonce_account_address} already exists and is not a nonce account")
536 };
537 return Err(CliError::BadParameter(err_msg).into());
538 }
539
540 if lamports < minimum_balance {
541 return Err(CliError::BadParameter(format!(
542 "need at least {minimum_balance} lamports for nonce account to be rent exempt, \
543 provided lamports: {lamports}"
544 ))
545 .into());
546 }
547
548 let mut tx = Transaction::new_unsigned(message);
549 tx.try_sign(&config.signers, latest_blockhash)?;
550 let result = rpc_client
551 .send_and_confirm_transaction_with_spinner_and_config(
552 &tx,
553 config.commitment,
554 config.send_transaction_config,
555 )
556 .await;
557
558 log_instruction_custom_error::<SystemError>(result, config)
559}
560
561pub async fn process_get_nonce(
562 rpc_client: &RpcClient,
563 config: &CliConfig<'_>,
564 nonce_account_pubkey: &Pubkey,
565) -> ProcessResult {
566 match get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment)
567 .await
568 .and_then(|ref a| state_from_account(a))?
569 {
570 State::Uninitialized => Ok("Nonce account is uninitialized".to_string()),
571 State::Initialized(ref data) => Ok(format!("{:?}", data.blockhash())),
572 }
573}
574
575pub async fn process_new_nonce(
576 rpc_client: &RpcClient,
577 config: &CliConfig<'_>,
578 nonce_account: &Pubkey,
579 nonce_authority: SignerIndex,
580 memo: Option<&String>,
581 compute_unit_price: Option<u64>,
582) -> ProcessResult {
583 check_unique_pubkeys(
584 (&config.signers[0].pubkey(), "cli keypair".to_string()),
585 (nonce_account, "nonce_account_pubkey".to_string()),
586 )?;
587
588 if let Err(err) = rpc_client.get_account(nonce_account).await {
589 return Err(CliError::BadParameter(format!(
590 "Unable to advance nonce account {nonce_account}. error: {err}"
591 ))
592 .into());
593 }
594
595 let nonce_authority = config.signers[nonce_authority];
596 let compute_unit_limit = ComputeUnitLimit::Simulated;
597 let ixs = vec![advance_nonce_account(
598 nonce_account,
599 &nonce_authority.pubkey(),
600 )]
601 .with_memo(memo)
602 .with_compute_unit_config(&ComputeUnitConfig {
603 compute_unit_price,
604 compute_unit_limit,
605 });
606 let latest_blockhash = rpc_client.get_latest_blockhash().await?;
607 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
608 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
609 let mut tx = Transaction::new_unsigned(message);
610 tx.try_sign(&config.signers, latest_blockhash)?;
611 check_account_for_fee_with_commitment(
612 rpc_client,
613 &config.signers[0].pubkey(),
614 &tx.message,
615 config.commitment,
616 )
617 .await?;
618 let result = rpc_client
619 .send_and_confirm_transaction_with_spinner_and_config(
620 &tx,
621 config.commitment,
622 config.send_transaction_config,
623 )
624 .await;
625
626 log_instruction_custom_error::<SystemError>(result, config)
627}
628
629pub async fn process_show_nonce_account(
630 rpc_client: &RpcClient,
631 config: &CliConfig<'_>,
632 nonce_account_pubkey: &Pubkey,
633 use_lamports_unit: bool,
634) -> ProcessResult {
635 let nonce_account =
636 get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment).await?;
637 let minimum_balance_for_rent_exemption = rpc_client
638 .get_minimum_balance_for_rent_exemption(State::size())
639 .await?;
640
641 let mut cli_nonce_account = CliNonceAccount {
642 balance: nonce_account.lamports,
643 minimum_balance_for_rent_exemption,
644 use_lamports_unit,
645 ..CliNonceAccount::default()
646 };
647
648 match state_from_account(&nonce_account)? {
649 State::Uninitialized => {}
650 State::Initialized(ref data) => {
651 cli_nonce_account.nonce = Some(data.blockhash().to_string());
652 cli_nonce_account.lamports_per_signature =
653 Some(data.fee_calculator.lamports_per_signature);
654 cli_nonce_account.authority = Some(data.authority.to_string());
655 }
656 }
657
658 Ok(config.output_format.formatted_string(&cli_nonce_account))
659}
660
661pub async fn process_withdraw_from_nonce_account(
662 rpc_client: &RpcClient,
663 config: &CliConfig<'_>,
664 nonce_account: &Pubkey,
665 nonce_authority: SignerIndex,
666 memo: Option<&String>,
667 destination_account_pubkey: &Pubkey,
668 lamports: u64,
669 compute_unit_price: Option<u64>,
670) -> ProcessResult {
671 let latest_blockhash = rpc_client.get_latest_blockhash().await?;
672
673 let nonce_authority = config.signers[nonce_authority];
674 let compute_unit_limit = ComputeUnitLimit::Simulated;
675 let ixs = vec![withdraw_nonce_account(
676 nonce_account,
677 &nonce_authority.pubkey(),
678 destination_account_pubkey,
679 lamports,
680 )]
681 .with_memo(memo)
682 .with_compute_unit_config(&ComputeUnitConfig {
683 compute_unit_price,
684 compute_unit_limit,
685 });
686 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
687 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
688 let mut tx = Transaction::new_unsigned(message);
689 tx.try_sign(&config.signers, latest_blockhash)?;
690 check_account_for_fee_with_commitment(
691 rpc_client,
692 &config.signers[0].pubkey(),
693 &tx.message,
694 config.commitment,
695 )
696 .await?;
697 let result = rpc_client
698 .send_and_confirm_transaction_with_spinner_and_config(
699 &tx,
700 config.commitment,
701 config.send_transaction_config,
702 )
703 .await;
704
705 log_instruction_custom_error::<SystemError>(result, config)
706}
707
708pub(crate) async fn process_upgrade_nonce_account(
709 rpc_client: &RpcClient,
710 config: &CliConfig<'_>,
711 nonce_account: Pubkey,
712 memo: Option<&String>,
713 compute_unit_price: Option<u64>,
714) -> ProcessResult {
715 let latest_blockhash = rpc_client.get_latest_blockhash().await?;
716 let compute_unit_limit = ComputeUnitLimit::Simulated;
717 let ixs = vec![upgrade_nonce_account(nonce_account)]
718 .with_memo(memo)
719 .with_compute_unit_config(&ComputeUnitConfig {
720 compute_unit_price,
721 compute_unit_limit,
722 });
723 let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
724 simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message).await?;
725 let mut tx = Transaction::new_unsigned(message);
726 tx.try_sign(&config.signers, latest_blockhash)?;
727 check_account_for_fee_with_commitment(
728 rpc_client,
729 &config.signers[0].pubkey(),
730 &tx.message,
731 config.commitment,
732 )
733 .await?;
734 let result = rpc_client
735 .send_and_confirm_transaction_with_spinner_and_config(
736 &tx,
737 config.commitment,
738 config.send_transaction_config,
739 )
740 .await;
741 log_instruction_custom_error::<SystemError>(result, config)
742}
743
744#[cfg(test)]
745mod tests {
746 use {
747 super::*,
748 crate::{clap_app::get_clap_app, cli::parse_command},
749 solana_account::{Account, state_traits::StateMut},
750 solana_keypair::{Keypair, read_keypair_file, write_keypair},
751 solana_nonce::{
752 self as nonce,
753 state::{DurableNonce, State},
754 versions::Versions,
755 },
756 solana_nonce_account as nonce_account,
757 solana_sdk_ids::system_program,
758 solana_sha256_hasher::hash,
759 solana_signer::Signer,
760 tempfile::NamedTempFile,
761 };
762
763 fn make_tmp_file() -> (String, NamedTempFile) {
764 let tmp_file = NamedTempFile::new().unwrap();
765 (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
766 }
767
768 #[test]
769 fn test_parse_command() {
770 let test_commands = get_clap_app("test", "desc", "version");
771 let default_keypair = Keypair::new();
772 let (default_keypair_file, mut tmp_file) = make_tmp_file();
773 write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
774 let default_signer = DefaultSigner::new("", &default_keypair_file);
775 let (keypair_file, mut tmp_file) = make_tmp_file();
776 let nonce_account_keypair = Keypair::new();
777 write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
778 let nonce_account_pubkey = nonce_account_keypair.pubkey();
779 let nonce_account_string = nonce_account_pubkey.to_string();
780
781 let (authority_keypair_file, mut tmp_file2) = make_tmp_file();
782 let nonce_authority_keypair = Keypair::new();
783 write_keypair(&nonce_authority_keypair, tmp_file2.as_file_mut()).unwrap();
784
785 let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
787 "test",
788 "authorize-nonce-account",
789 &keypair_file,
790 &Pubkey::default().to_string(),
791 ]);
792 assert_eq!(
793 parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
794 CliCommandInfo {
795 command: CliCommand::AuthorizeNonceAccount {
796 nonce_account: nonce_account_pubkey,
797 nonce_authority: 0,
798 memo: None,
799 new_authority: Pubkey::default(),
800 compute_unit_price: None,
801 },
802 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
803 }
804 );
805
806 let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
808 "test",
809 "authorize-nonce-account",
810 &keypair_file,
811 &Pubkey::default().to_string(),
812 "--nonce-authority",
813 &authority_keypair_file,
814 ]);
815 assert_eq!(
816 parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
817 CliCommandInfo {
818 command: CliCommand::AuthorizeNonceAccount {
819 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
820 nonce_authority: 1,
821 memo: None,
822 new_authority: Pubkey::default(),
823 compute_unit_price: None,
824 },
825 signers: vec![
826 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
827 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
828 ],
829 }
830 );
831
832 let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
834 "test",
835 "create-nonce-account",
836 &keypair_file,
837 "50",
838 ]);
839 assert_eq!(
840 parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
841 CliCommandInfo {
842 command: CliCommand::CreateNonceAccount {
843 nonce_account: 1,
844 seed: None,
845 nonce_authority: None,
846 memo: None,
847 amount: SpendAmount::Some(50_000_000_000),
848 compute_unit_price: None,
849 },
850 signers: vec![
851 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
852 Box::new(read_keypair_file(&keypair_file).unwrap())
853 ],
854 }
855 );
856
857 let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
859 "test",
860 "create-nonce-account",
861 &keypair_file,
862 "50",
863 "--nonce-authority",
864 &authority_keypair_file,
865 ]);
866 assert_eq!(
867 parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
868 CliCommandInfo {
869 command: CliCommand::CreateNonceAccount {
870 nonce_account: 1,
871 seed: None,
872 nonce_authority: Some(nonce_authority_keypair.pubkey()),
873 memo: None,
874 amount: SpendAmount::Some(50_000_000_000),
875 compute_unit_price: None,
876 },
877 signers: vec![
878 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
879 Box::new(read_keypair_file(&keypair_file).unwrap())
880 ],
881 }
882 );
883
884 let test_get_nonce = test_commands.clone().get_matches_from(vec![
886 "test",
887 "get-nonce",
888 &nonce_account_string,
889 ]);
890 assert_eq!(
891 parse_command(&test_get_nonce, &default_signer, &mut None).unwrap(),
892 CliCommandInfo::without_signers(CliCommand::GetNonce(nonce_account_keypair.pubkey()))
893 );
894
895 let test_new_nonce =
897 test_commands
898 .clone()
899 .get_matches_from(vec!["test", "new-nonce", &keypair_file]);
900 let nonce_account = read_keypair_file(&keypair_file).unwrap();
901 assert_eq!(
902 parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
903 CliCommandInfo {
904 command: CliCommand::NewNonce {
905 nonce_account: nonce_account.pubkey(),
906 nonce_authority: 0,
907 memo: None,
908 compute_unit_price: None,
909 },
910 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
911 }
912 );
913
914 let test_new_nonce = test_commands.clone().get_matches_from(vec![
916 "test",
917 "new-nonce",
918 &keypair_file,
919 "--nonce-authority",
920 &authority_keypair_file,
921 ]);
922 let nonce_account = read_keypair_file(&keypair_file).unwrap();
923 assert_eq!(
924 parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
925 CliCommandInfo {
926 command: CliCommand::NewNonce {
927 nonce_account: nonce_account.pubkey(),
928 nonce_authority: 1,
929 memo: None,
930 compute_unit_price: None,
931 },
932 signers: vec![
933 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
934 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
935 ],
936 }
937 );
938
939 let test_show_nonce_account = test_commands.clone().get_matches_from(vec![
941 "test",
942 "nonce-account",
943 &nonce_account_string,
944 ]);
945 assert_eq!(
946 parse_command(&test_show_nonce_account, &default_signer, &mut None).unwrap(),
947 CliCommandInfo::without_signers(CliCommand::ShowNonceAccount {
948 nonce_account_pubkey: nonce_account_keypair.pubkey(),
949 use_lamports_unit: false,
950 })
951 );
952
953 let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
955 "test",
956 "withdraw-from-nonce-account",
957 &keypair_file,
958 &nonce_account_string,
959 "42",
960 ]);
961 assert_eq!(
962 parse_command(
963 &test_withdraw_from_nonce_account,
964 &default_signer,
965 &mut None
966 )
967 .unwrap(),
968 CliCommandInfo {
969 command: CliCommand::WithdrawFromNonceAccount {
970 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
971 nonce_authority: 0,
972 memo: None,
973 destination_account_pubkey: nonce_account_pubkey,
974 lamports: 42_000_000_000,
975 compute_unit_price: None,
976 },
977 signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
978 }
979 );
980
981 let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
983 "test",
984 "withdraw-from-nonce-account",
985 &keypair_file,
986 &nonce_account_string,
987 "42",
988 "--nonce-authority",
989 &authority_keypair_file,
990 ]);
991 assert_eq!(
992 parse_command(
993 &test_withdraw_from_nonce_account,
994 &default_signer,
995 &mut None
996 )
997 .unwrap(),
998 CliCommandInfo {
999 command: CliCommand::WithdrawFromNonceAccount {
1000 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
1001 nonce_authority: 1,
1002 memo: None,
1003 destination_account_pubkey: nonce_account_pubkey,
1004 lamports: 42_000_000_000,
1005 compute_unit_price: None,
1006 },
1007 signers: vec![
1008 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1009 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
1010 ],
1011 }
1012 );
1013
1014 let test_upgrade_nonce_account = test_commands.clone().get_matches_from(vec![
1016 "test",
1017 "upgrade-nonce-account",
1018 &nonce_account_string,
1019 ]);
1020 assert_eq!(
1021 parse_command(&test_upgrade_nonce_account, &default_signer, &mut None).unwrap(),
1022 CliCommandInfo {
1023 command: CliCommand::UpgradeNonceAccount {
1024 nonce_account: nonce_account_pubkey,
1025 memo: None,
1026 compute_unit_price: None,
1027 },
1028 signers: CliSigners::default(),
1029 }
1030 );
1031
1032 let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
1034 "test",
1035 "authorize-nonce-account",
1036 &keypair_file,
1037 &Pubkey::default().to_string(),
1038 "--nonce-authority",
1039 &authority_keypair_file,
1040 "--with-compute-unit-price",
1041 "99",
1042 ]);
1043 assert_eq!(
1044 parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
1045 CliCommandInfo {
1046 command: CliCommand::AuthorizeNonceAccount {
1047 nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
1048 nonce_authority: 1,
1049 memo: None,
1050 new_authority: Pubkey::default(),
1051 compute_unit_price: Some(99),
1052 },
1053 signers: vec![
1054 Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1055 Box::new(read_keypair_file(&authority_keypair_file).unwrap())
1056 ],
1057 }
1058 );
1059 }
1060
1061 #[test]
1062 fn test_check_nonce_account() {
1063 let durable_nonce = DurableNonce::from_blockhash(&Hash::default());
1064 let blockhash = *durable_nonce.as_hash();
1065 let nonce_pubkey = solana_pubkey::new_rand();
1066 let data = Versions::new(State::Initialized(nonce::state::Data::new(
1067 nonce_pubkey,
1068 durable_nonce,
1069 0,
1070 )));
1071 let valid = Account::new_data(1, &data, &system_program::ID);
1072 assert!(check_nonce_account(&valid.unwrap(), &nonce_pubkey, &blockhash).is_ok());
1073
1074 let invalid_owner = Account::new_data(1, &data, &Pubkey::from([1u8; 32]));
1075 if let CliError::InvalidNonce(err) =
1076 check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1077 {
1078 assert_eq!(err, Error::InvalidAccountOwner,);
1079 }
1080
1081 let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
1082 if let CliError::InvalidNonce(err) =
1083 check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1084 {
1085 assert_eq!(err, Error::InvalidAccountData,);
1086 }
1087
1088 let invalid_durable_nonce = DurableNonce::from_blockhash(&hash(b"invalid"));
1089 let data = Versions::new(State::Initialized(nonce::state::Data::new(
1090 nonce_pubkey,
1091 invalid_durable_nonce,
1092 0,
1093 )));
1094 let invalid_hash = Account::new_data(1, &data, &system_program::ID).unwrap();
1095 if let CliError::InvalidNonce(err) =
1096 check_nonce_account(&invalid_hash, &nonce_pubkey, &blockhash).unwrap_err()
1097 {
1098 assert_eq!(
1099 err,
1100 Error::InvalidHash {
1101 provided: blockhash,
1102 expected: *invalid_durable_nonce.as_hash(),
1103 }
1104 );
1105 }
1106
1107 let new_nonce_authority = solana_pubkey::new_rand();
1108 let data = Versions::new(State::Initialized(nonce::state::Data::new(
1109 new_nonce_authority,
1110 durable_nonce,
1111 0,
1112 )));
1113 let invalid_authority = Account::new_data(1, &data, &system_program::ID);
1114 if let CliError::InvalidNonce(err) =
1115 check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1116 {
1117 assert_eq!(
1118 err,
1119 Error::InvalidAuthority {
1120 provided: nonce_pubkey,
1121 expected: new_nonce_authority,
1122 }
1123 );
1124 }
1125
1126 let data = Versions::new(State::Uninitialized);
1127 let invalid_state = Account::new_data(1, &data, &system_program::ID);
1128 if let CliError::InvalidNonce(err) =
1129 check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1130 {
1131 assert_eq!(err, Error::InvalidStateForOperation,);
1132 }
1133 }
1134
1135 #[test]
1136 fn test_account_identity_ok() {
1137 let nonce_account = nonce_account::create_account(1).into_inner();
1138 assert_eq!(account_identity_ok(&nonce_account), Ok(()));
1139
1140 let system_account = Account::new(1, 0, &system_program::id());
1141 assert_eq!(
1142 account_identity_ok(&system_account),
1143 Err(Error::UnexpectedDataSize),
1144 );
1145
1146 let other_program = Pubkey::from([1u8; 32]);
1147 let other_account_no_data = Account::new(1, 0, &other_program);
1148 assert_eq!(
1149 account_identity_ok(&other_account_no_data),
1150 Err(Error::InvalidAccountOwner),
1151 );
1152 }
1153
1154 #[test]
1155 fn test_state_from_account() {
1156 let mut nonce_account = nonce_account::create_account(1).into_inner();
1157 assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized));
1158
1159 let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1160 let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1161 nonce_account
1162 .set_state(&Versions::new(State::Initialized(data.clone())))
1163 .unwrap();
1164 assert_eq!(
1165 state_from_account(&nonce_account),
1166 Ok(State::Initialized(data))
1167 );
1168
1169 let wrong_data_size_account = Account::new(1, 1, &system_program::id());
1170 assert_eq!(
1171 state_from_account(&wrong_data_size_account),
1172 Err(Error::InvalidAccountData),
1173 );
1174 }
1175
1176 #[test]
1177 fn test_data_from_helpers() {
1178 let mut nonce_account = nonce_account::create_account(1).into_inner();
1179 let state = state_from_account(&nonce_account).unwrap();
1180 assert_eq!(
1181 data_from_state(&state),
1182 Err(Error::InvalidStateForOperation)
1183 );
1184 assert_eq!(
1185 data_from_account(&nonce_account),
1186 Err(Error::InvalidStateForOperation)
1187 );
1188
1189 let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1190 let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1191 nonce_account
1192 .set_state(&Versions::new(State::Initialized(data.clone())))
1193 .unwrap();
1194 let state = state_from_account(&nonce_account).unwrap();
1195 assert_eq!(data_from_state(&state), Ok(&data));
1196 assert_eq!(data_from_account(&nonce_account), Ok(data));
1197 }
1198}