solana_cli/
nonce.rs

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        spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
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::{self as nonce, state::State},
28    solana_pubkey::Pubkey,
29    solana_remote_wallet::remote_wallet::RemoteWalletManager,
30    solana_rpc_client::rpc_client::RpcClient,
31    solana_rpc_client_nonce_utils::*,
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
378/// Check if a nonce account is initialized with the given authority and hash
379pub 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 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()?;
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)?;
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    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
441        &tx,
442        config.commitment,
443        config.send_transaction_config,
444    );
445
446    log_instruction_custom_error::<SystemError>(result, config)
447}
448
449pub fn process_create_nonce_account(
450    rpc_client: &RpcClient,
451    config: &CliConfig,
452    nonce_account: SignerIndex,
453    seed: Option<String>,
454    nonce_authority: Option<Pubkey>,
455    memo: Option<&String>,
456    mut amount: SpendAmount,
457    compute_unit_price: Option<u64>,
458) -> ProcessResult {
459    let nonce_account_pubkey = config.signers[nonce_account].pubkey();
460    let nonce_account_address = if let Some(ref seed) = seed {
461        Pubkey::create_with_seed(&nonce_account_pubkey, seed, &system_program::id())?
462    } else {
463        nonce_account_pubkey
464    };
465
466    check_unique_pubkeys(
467        (&config.signers[0].pubkey(), "cli keypair".to_string()),
468        (&nonce_account_address, "nonce_account".to_string()),
469    )?;
470
471    let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(State::size())?;
472    if amount == SpendAmount::All {
473        amount = SpendAmount::AllForAccountCreation {
474            create_account_min_balance: minimum_balance,
475        };
476    }
477
478    let nonce_authority = nonce_authority.unwrap_or_else(|| config.signers[0].pubkey());
479
480    let compute_unit_limit = ComputeUnitLimit::Simulated;
481    let build_message = |lamports| {
482        let ixs = if let Some(seed) = seed.clone() {
483            create_nonce_account_with_seed(
484                &config.signers[0].pubkey(), // from
485                &nonce_account_address,      // to
486                &nonce_account_pubkey,       // base
487                &seed,                       // seed
488                &nonce_authority,
489                lamports,
490            )
491            .with_memo(memo)
492            .with_compute_unit_config(&ComputeUnitConfig {
493                compute_unit_price,
494                compute_unit_limit,
495            })
496        } else {
497            create_nonce_account(
498                &config.signers[0].pubkey(),
499                &nonce_account_pubkey,
500                &nonce_authority,
501                lamports,
502            )
503            .with_memo(memo)
504            .with_compute_unit_config(&ComputeUnitConfig {
505                compute_unit_price,
506                compute_unit_limit,
507            })
508        };
509        Message::new(&ixs, Some(&config.signers[0].pubkey()))
510    };
511
512    let latest_blockhash = rpc_client.get_latest_blockhash()?;
513
514    let (message, lamports) = resolve_spend_tx_and_check_account_balance(
515        rpc_client,
516        false,
517        amount,
518        &latest_blockhash,
519        &config.signers[0].pubkey(),
520        compute_unit_limit,
521        build_message,
522        config.commitment,
523    )?;
524
525    if let Ok(nonce_account) = get_account(rpc_client, &nonce_account_address) {
526        let err_msg = if state_from_account(&nonce_account).is_ok() {
527            format!("Nonce account {nonce_account_address} already exists")
528        } else {
529            format!("Account {nonce_account_address} already exists and is not a nonce account")
530        };
531        return Err(CliError::BadParameter(err_msg).into());
532    }
533
534    if lamports < minimum_balance {
535        return Err(CliError::BadParameter(format!(
536            "need at least {minimum_balance} lamports for nonce account to be rent exempt, \
537             provided lamports: {lamports}"
538        ))
539        .into());
540    }
541
542    let mut tx = Transaction::new_unsigned(message);
543    tx.try_sign(&config.signers, latest_blockhash)?;
544    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
545        &tx,
546        config.commitment,
547        config.send_transaction_config,
548    );
549
550    log_instruction_custom_error::<SystemError>(result, config)
551}
552
553pub fn process_get_nonce(
554    rpc_client: &RpcClient,
555    config: &CliConfig,
556    nonce_account_pubkey: &Pubkey,
557) -> ProcessResult {
558    match get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment)
559        .and_then(|ref a| state_from_account(a))?
560    {
561        State::Uninitialized => Ok("Nonce account is uninitialized".to_string()),
562        State::Initialized(ref data) => Ok(format!("{:?}", data.blockhash())),
563    }
564}
565
566pub fn process_new_nonce(
567    rpc_client: &RpcClient,
568    config: &CliConfig,
569    nonce_account: &Pubkey,
570    nonce_authority: SignerIndex,
571    memo: Option<&String>,
572    compute_unit_price: Option<u64>,
573) -> ProcessResult {
574    check_unique_pubkeys(
575        (&config.signers[0].pubkey(), "cli keypair".to_string()),
576        (nonce_account, "nonce_account_pubkey".to_string()),
577    )?;
578
579    if let Err(err) = rpc_client.get_account(nonce_account) {
580        return Err(CliError::BadParameter(format!(
581            "Unable to advance nonce account {nonce_account}. error: {err}"
582        ))
583        .into());
584    }
585
586    let nonce_authority = config.signers[nonce_authority];
587    let compute_unit_limit = ComputeUnitLimit::Simulated;
588    let ixs = vec![advance_nonce_account(
589        nonce_account,
590        &nonce_authority.pubkey(),
591    )]
592    .with_memo(memo)
593    .with_compute_unit_config(&ComputeUnitConfig {
594        compute_unit_price,
595        compute_unit_limit,
596    });
597    let latest_blockhash = rpc_client.get_latest_blockhash()?;
598    let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
599    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
600    let mut tx = Transaction::new_unsigned(message);
601    tx.try_sign(&config.signers, latest_blockhash)?;
602    check_account_for_fee_with_commitment(
603        rpc_client,
604        &config.signers[0].pubkey(),
605        &tx.message,
606        config.commitment,
607    )?;
608    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
609        &tx,
610        config.commitment,
611        config.send_transaction_config,
612    );
613
614    log_instruction_custom_error::<SystemError>(result, config)
615}
616
617pub fn process_show_nonce_account(
618    rpc_client: &RpcClient,
619    config: &CliConfig,
620    nonce_account_pubkey: &Pubkey,
621    use_lamports_unit: bool,
622) -> ProcessResult {
623    let nonce_account =
624        get_account_with_commitment(rpc_client, nonce_account_pubkey, config.commitment)?;
625    let print_account = |data: Option<&nonce::state::Data>| {
626        let mut nonce_account = CliNonceAccount {
627            balance: nonce_account.lamports,
628            minimum_balance_for_rent_exemption: rpc_client
629                .get_minimum_balance_for_rent_exemption(State::size())?,
630            use_lamports_unit,
631            ..CliNonceAccount::default()
632        };
633        if let Some(data) = data {
634            nonce_account.nonce = Some(data.blockhash().to_string());
635            nonce_account.lamports_per_signature = Some(data.fee_calculator.lamports_per_signature);
636            nonce_account.authority = Some(data.authority.to_string());
637        }
638
639        Ok(config.output_format.formatted_string(&nonce_account))
640    };
641    match state_from_account(&nonce_account)? {
642        State::Uninitialized => print_account(None),
643        State::Initialized(ref data) => print_account(Some(data)),
644    }
645}
646
647pub fn process_withdraw_from_nonce_account(
648    rpc_client: &RpcClient,
649    config: &CliConfig,
650    nonce_account: &Pubkey,
651    nonce_authority: SignerIndex,
652    memo: Option<&String>,
653    destination_account_pubkey: &Pubkey,
654    lamports: u64,
655    compute_unit_price: Option<u64>,
656) -> ProcessResult {
657    let latest_blockhash = rpc_client.get_latest_blockhash()?;
658
659    let nonce_authority = config.signers[nonce_authority];
660    let compute_unit_limit = ComputeUnitLimit::Simulated;
661    let ixs = vec![withdraw_nonce_account(
662        nonce_account,
663        &nonce_authority.pubkey(),
664        destination_account_pubkey,
665        lamports,
666    )]
667    .with_memo(memo)
668    .with_compute_unit_config(&ComputeUnitConfig {
669        compute_unit_price,
670        compute_unit_limit,
671    });
672    let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
673    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
674    let mut tx = Transaction::new_unsigned(message);
675    tx.try_sign(&config.signers, latest_blockhash)?;
676    check_account_for_fee_with_commitment(
677        rpc_client,
678        &config.signers[0].pubkey(),
679        &tx.message,
680        config.commitment,
681    )?;
682    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
683        &tx,
684        config.commitment,
685        config.send_transaction_config,
686    );
687
688    log_instruction_custom_error::<SystemError>(result, config)
689}
690
691pub(crate) fn process_upgrade_nonce_account(
692    rpc_client: &RpcClient,
693    config: &CliConfig,
694    nonce_account: Pubkey,
695    memo: Option<&String>,
696    compute_unit_price: Option<u64>,
697) -> ProcessResult {
698    let latest_blockhash = rpc_client.get_latest_blockhash()?;
699    let compute_unit_limit = ComputeUnitLimit::Simulated;
700    let ixs = vec![upgrade_nonce_account(nonce_account)]
701        .with_memo(memo)
702        .with_compute_unit_config(&ComputeUnitConfig {
703            compute_unit_price,
704            compute_unit_limit,
705        });
706    let mut message = Message::new(&ixs, Some(&config.signers[0].pubkey()));
707    simulate_and_update_compute_unit_limit(&compute_unit_limit, rpc_client, &mut message)?;
708    let mut tx = Transaction::new_unsigned(message);
709    tx.try_sign(&config.signers, latest_blockhash)?;
710    check_account_for_fee_with_commitment(
711        rpc_client,
712        &config.signers[0].pubkey(),
713        &tx.message,
714        config.commitment,
715    )?;
716    let result = rpc_client.send_and_confirm_transaction_with_spinner_and_config(
717        &tx,
718        config.commitment,
719        config.send_transaction_config,
720    );
721    log_instruction_custom_error::<SystemError>(result, config)
722}
723
724#[cfg(test)]
725mod tests {
726    use {
727        super::*,
728        crate::{clap_app::get_clap_app, cli::parse_command},
729        solana_account::{state_traits::StateMut, Account},
730        solana_keypair::{read_keypair_file, write_keypair, Keypair},
731        solana_nonce::{
732            self as nonce,
733            state::{DurableNonce, State},
734            versions::Versions,
735        },
736        solana_nonce_account as nonce_account,
737        solana_sdk_ids::system_program,
738        solana_sha256_hasher::hash,
739        solana_signer::Signer,
740        tempfile::NamedTempFile,
741    };
742
743    fn make_tmp_file() -> (String, NamedTempFile) {
744        let tmp_file = NamedTempFile::new().unwrap();
745        (String::from(tmp_file.path().to_str().unwrap()), tmp_file)
746    }
747
748    #[test]
749    fn test_parse_command() {
750        let test_commands = get_clap_app("test", "desc", "version");
751        let default_keypair = Keypair::new();
752        let (default_keypair_file, mut tmp_file) = make_tmp_file();
753        write_keypair(&default_keypair, tmp_file.as_file_mut()).unwrap();
754        let default_signer = DefaultSigner::new("", &default_keypair_file);
755        let (keypair_file, mut tmp_file) = make_tmp_file();
756        let nonce_account_keypair = Keypair::new();
757        write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap();
758        let nonce_account_pubkey = nonce_account_keypair.pubkey();
759        let nonce_account_string = nonce_account_pubkey.to_string();
760
761        let (authority_keypair_file, mut tmp_file2) = make_tmp_file();
762        let nonce_authority_keypair = Keypair::new();
763        write_keypair(&nonce_authority_keypair, tmp_file2.as_file_mut()).unwrap();
764
765        // Test AuthorizeNonceAccount Subcommand
766        let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
767            "test",
768            "authorize-nonce-account",
769            &keypair_file,
770            &Pubkey::default().to_string(),
771        ]);
772        assert_eq!(
773            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
774            CliCommandInfo {
775                command: CliCommand::AuthorizeNonceAccount {
776                    nonce_account: nonce_account_pubkey,
777                    nonce_authority: 0,
778                    memo: None,
779                    new_authority: Pubkey::default(),
780                    compute_unit_price: None,
781                },
782                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
783            }
784        );
785
786        // Test AuthorizeNonceAccount Subcommand with authority
787        let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
788            "test",
789            "authorize-nonce-account",
790            &keypair_file,
791            &Pubkey::default().to_string(),
792            "--nonce-authority",
793            &authority_keypair_file,
794        ]);
795        assert_eq!(
796            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
797            CliCommandInfo {
798                command: CliCommand::AuthorizeNonceAccount {
799                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
800                    nonce_authority: 1,
801                    memo: None,
802                    new_authority: Pubkey::default(),
803                    compute_unit_price: None,
804                },
805                signers: vec![
806                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
807                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
808                ],
809            }
810        );
811
812        // Test CreateNonceAccount SubCommand
813        let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
814            "test",
815            "create-nonce-account",
816            &keypair_file,
817            "50",
818        ]);
819        assert_eq!(
820            parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
821            CliCommandInfo {
822                command: CliCommand::CreateNonceAccount {
823                    nonce_account: 1,
824                    seed: None,
825                    nonce_authority: None,
826                    memo: None,
827                    amount: SpendAmount::Some(50_000_000_000),
828                    compute_unit_price: None,
829                },
830                signers: vec![
831                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
832                    Box::new(read_keypair_file(&keypair_file).unwrap())
833                ],
834            }
835        );
836
837        // Test CreateNonceAccount SubCommand with authority
838        let test_create_nonce_account = test_commands.clone().get_matches_from(vec![
839            "test",
840            "create-nonce-account",
841            &keypair_file,
842            "50",
843            "--nonce-authority",
844            &authority_keypair_file,
845        ]);
846        assert_eq!(
847            parse_command(&test_create_nonce_account, &default_signer, &mut None).unwrap(),
848            CliCommandInfo {
849                command: CliCommand::CreateNonceAccount {
850                    nonce_account: 1,
851                    seed: None,
852                    nonce_authority: Some(nonce_authority_keypair.pubkey()),
853                    memo: None,
854                    amount: SpendAmount::Some(50_000_000_000),
855                    compute_unit_price: None,
856                },
857                signers: vec![
858                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
859                    Box::new(read_keypair_file(&keypair_file).unwrap())
860                ],
861            }
862        );
863
864        // Test GetNonce Subcommand
865        let test_get_nonce = test_commands.clone().get_matches_from(vec![
866            "test",
867            "get-nonce",
868            &nonce_account_string,
869        ]);
870        assert_eq!(
871            parse_command(&test_get_nonce, &default_signer, &mut None).unwrap(),
872            CliCommandInfo::without_signers(CliCommand::GetNonce(nonce_account_keypair.pubkey()))
873        );
874
875        // Test NewNonce SubCommand
876        let test_new_nonce =
877            test_commands
878                .clone()
879                .get_matches_from(vec!["test", "new-nonce", &keypair_file]);
880        let nonce_account = read_keypair_file(&keypair_file).unwrap();
881        assert_eq!(
882            parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
883            CliCommandInfo {
884                command: CliCommand::NewNonce {
885                    nonce_account: nonce_account.pubkey(),
886                    nonce_authority: 0,
887                    memo: None,
888                    compute_unit_price: None,
889                },
890                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
891            }
892        );
893
894        // Test NewNonce SubCommand with authority
895        let test_new_nonce = test_commands.clone().get_matches_from(vec![
896            "test",
897            "new-nonce",
898            &keypair_file,
899            "--nonce-authority",
900            &authority_keypair_file,
901        ]);
902        let nonce_account = read_keypair_file(&keypair_file).unwrap();
903        assert_eq!(
904            parse_command(&test_new_nonce, &default_signer, &mut None).unwrap(),
905            CliCommandInfo {
906                command: CliCommand::NewNonce {
907                    nonce_account: nonce_account.pubkey(),
908                    nonce_authority: 1,
909                    memo: None,
910                    compute_unit_price: None,
911                },
912                signers: vec![
913                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
914                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
915                ],
916            }
917        );
918
919        // Test ShowNonceAccount Subcommand
920        let test_show_nonce_account = test_commands.clone().get_matches_from(vec![
921            "test",
922            "nonce-account",
923            &nonce_account_string,
924        ]);
925        assert_eq!(
926            parse_command(&test_show_nonce_account, &default_signer, &mut None).unwrap(),
927            CliCommandInfo::without_signers(CliCommand::ShowNonceAccount {
928                nonce_account_pubkey: nonce_account_keypair.pubkey(),
929                use_lamports_unit: false,
930            })
931        );
932
933        // Test WithdrawFromNonceAccount Subcommand
934        let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
935            "test",
936            "withdraw-from-nonce-account",
937            &keypair_file,
938            &nonce_account_string,
939            "42",
940        ]);
941        assert_eq!(
942            parse_command(
943                &test_withdraw_from_nonce_account,
944                &default_signer,
945                &mut None
946            )
947            .unwrap(),
948            CliCommandInfo {
949                command: CliCommand::WithdrawFromNonceAccount {
950                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
951                    nonce_authority: 0,
952                    memo: None,
953                    destination_account_pubkey: nonce_account_pubkey,
954                    lamports: 42_000_000_000,
955                    compute_unit_price: None,
956                },
957                signers: vec![Box::new(read_keypair_file(&default_keypair_file).unwrap())],
958            }
959        );
960
961        // Test WithdrawFromNonceAccount Subcommand with authority
962        let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![
963            "test",
964            "withdraw-from-nonce-account",
965            &keypair_file,
966            &nonce_account_string,
967            "42",
968            "--nonce-authority",
969            &authority_keypair_file,
970        ]);
971        assert_eq!(
972            parse_command(
973                &test_withdraw_from_nonce_account,
974                &default_signer,
975                &mut None
976            )
977            .unwrap(),
978            CliCommandInfo {
979                command: CliCommand::WithdrawFromNonceAccount {
980                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
981                    nonce_authority: 1,
982                    memo: None,
983                    destination_account_pubkey: nonce_account_pubkey,
984                    lamports: 42_000_000_000,
985                    compute_unit_price: None,
986                },
987                signers: vec![
988                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
989                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
990                ],
991            }
992        );
993
994        // Test UpgradeNonceAccount Subcommand.
995        let test_upgrade_nonce_account = test_commands.clone().get_matches_from(vec![
996            "test",
997            "upgrade-nonce-account",
998            &nonce_account_string,
999        ]);
1000        assert_eq!(
1001            parse_command(&test_upgrade_nonce_account, &default_signer, &mut None).unwrap(),
1002            CliCommandInfo {
1003                command: CliCommand::UpgradeNonceAccount {
1004                    nonce_account: nonce_account_pubkey,
1005                    memo: None,
1006                    compute_unit_price: None,
1007                },
1008                signers: CliSigners::default(),
1009            }
1010        );
1011
1012        // Test ComputeUnitPrice Subcommand with authority
1013        let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![
1014            "test",
1015            "authorize-nonce-account",
1016            &keypair_file,
1017            &Pubkey::default().to_string(),
1018            "--nonce-authority",
1019            &authority_keypair_file,
1020            "--with-compute-unit-price",
1021            "99",
1022        ]);
1023        assert_eq!(
1024            parse_command(&test_authorize_nonce_account, &default_signer, &mut None).unwrap(),
1025            CliCommandInfo {
1026                command: CliCommand::AuthorizeNonceAccount {
1027                    nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(),
1028                    nonce_authority: 1,
1029                    memo: None,
1030                    new_authority: Pubkey::default(),
1031                    compute_unit_price: Some(99),
1032                },
1033                signers: vec![
1034                    Box::new(read_keypair_file(&default_keypair_file).unwrap()),
1035                    Box::new(read_keypair_file(&authority_keypair_file).unwrap())
1036                ],
1037            }
1038        );
1039    }
1040
1041    #[test]
1042    fn test_check_nonce_account() {
1043        let durable_nonce = DurableNonce::from_blockhash(&Hash::default());
1044        let blockhash = *durable_nonce.as_hash();
1045        let nonce_pubkey = solana_pubkey::new_rand();
1046        let data = Versions::new(State::Initialized(nonce::state::Data::new(
1047            nonce_pubkey,
1048            durable_nonce,
1049            0,
1050        )));
1051        let valid = Account::new_data(1, &data, &system_program::ID);
1052        assert!(check_nonce_account(&valid.unwrap(), &nonce_pubkey, &blockhash).is_ok());
1053
1054        let invalid_owner = Account::new_data(1, &data, &Pubkey::from([1u8; 32]));
1055        if let CliError::InvalidNonce(err) =
1056            check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1057        {
1058            assert_eq!(err, Error::InvalidAccountOwner,);
1059        }
1060
1061        let invalid_data = Account::new_data(1, &"invalid", &system_program::ID);
1062        if let CliError::InvalidNonce(err) =
1063            check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1064        {
1065            assert_eq!(err, Error::InvalidAccountData,);
1066        }
1067
1068        let invalid_durable_nonce = DurableNonce::from_blockhash(&hash(b"invalid"));
1069        let data = Versions::new(State::Initialized(nonce::state::Data::new(
1070            nonce_pubkey,
1071            invalid_durable_nonce,
1072            0,
1073        )));
1074        let invalid_hash = Account::new_data(1, &data, &system_program::ID).unwrap();
1075        if let CliError::InvalidNonce(err) =
1076            check_nonce_account(&invalid_hash, &nonce_pubkey, &blockhash).unwrap_err()
1077        {
1078            assert_eq!(
1079                err,
1080                Error::InvalidHash {
1081                    provided: blockhash,
1082                    expected: *invalid_durable_nonce.as_hash(),
1083                }
1084            );
1085        }
1086
1087        let new_nonce_authority = solana_pubkey::new_rand();
1088        let data = Versions::new(State::Initialized(nonce::state::Data::new(
1089            new_nonce_authority,
1090            durable_nonce,
1091            0,
1092        )));
1093        let invalid_authority = Account::new_data(1, &data, &system_program::ID);
1094        if let CliError::InvalidNonce(err) =
1095            check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1096        {
1097            assert_eq!(
1098                err,
1099                Error::InvalidAuthority {
1100                    provided: nonce_pubkey,
1101                    expected: new_nonce_authority,
1102                }
1103            );
1104        }
1105
1106        let data = Versions::new(State::Uninitialized);
1107        let invalid_state = Account::new_data(1, &data, &system_program::ID);
1108        if let CliError::InvalidNonce(err) =
1109            check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash).unwrap_err()
1110        {
1111            assert_eq!(err, Error::InvalidStateForOperation,);
1112        }
1113    }
1114
1115    #[test]
1116    fn test_account_identity_ok() {
1117        let nonce_account = nonce_account::create_account(1).into_inner();
1118        assert_eq!(account_identity_ok(&nonce_account), Ok(()));
1119
1120        let system_account = Account::new(1, 0, &system_program::id());
1121        assert_eq!(
1122            account_identity_ok(&system_account),
1123            Err(Error::UnexpectedDataSize),
1124        );
1125
1126        let other_program = Pubkey::from([1u8; 32]);
1127        let other_account_no_data = Account::new(1, 0, &other_program);
1128        assert_eq!(
1129            account_identity_ok(&other_account_no_data),
1130            Err(Error::InvalidAccountOwner),
1131        );
1132    }
1133
1134    #[test]
1135    fn test_state_from_account() {
1136        let mut nonce_account = nonce_account::create_account(1).into_inner();
1137        assert_eq!(state_from_account(&nonce_account), Ok(State::Uninitialized));
1138
1139        let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1140        let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1141        nonce_account
1142            .set_state(&Versions::new(State::Initialized(data.clone())))
1143            .unwrap();
1144        assert_eq!(
1145            state_from_account(&nonce_account),
1146            Ok(State::Initialized(data))
1147        );
1148
1149        let wrong_data_size_account = Account::new(1, 1, &system_program::id());
1150        assert_eq!(
1151            state_from_account(&wrong_data_size_account),
1152            Err(Error::InvalidAccountData),
1153        );
1154    }
1155
1156    #[test]
1157    fn test_data_from_helpers() {
1158        let mut nonce_account = nonce_account::create_account(1).into_inner();
1159        let state = state_from_account(&nonce_account).unwrap();
1160        assert_eq!(
1161            data_from_state(&state),
1162            Err(Error::InvalidStateForOperation)
1163        );
1164        assert_eq!(
1165            data_from_account(&nonce_account),
1166            Err(Error::InvalidStateForOperation)
1167        );
1168
1169        let durable_nonce = DurableNonce::from_blockhash(&Hash::new_from_array([42u8; 32]));
1170        let data = nonce::state::Data::new(Pubkey::from([1u8; 32]), durable_nonce, 42);
1171        nonce_account
1172            .set_state(&Versions::new(State::Initialized(data.clone())))
1173            .unwrap();
1174        let state = state_from_account(&nonce_account).unwrap();
1175        assert_eq!(data_from_state(&state), Ok(&data));
1176        assert_eq!(data_from_account(&nonce_account), Ok(data));
1177    }
1178}