use crate::cli::{ build_balance_message, check_account_for_fee, check_unique_pubkeys, log_instruction_custom_error, required_lamports_from, CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult, SigningAuthority, }; use crate::offline::BLOCKHASH_ARG; use clap::{App, Arg, ArgMatches, SubCommand}; use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant}; use solana_client::rpc_client::RpcClient; use solana_sdk::{ account::Account, account_utils::StateMut, hash::Hash, nonce_state::{Meta, NonceState}, pubkey::Pubkey, signature::{Keypair, KeypairUtil}, system_instruction::{ advance_nonce_account, authorize_nonce_account, create_address_with_seed, create_nonce_account, create_nonce_account_with_seed, withdraw_nonce_account, NonceError, SystemError, }, system_program, transaction::Transaction, }; #[derive(Debug, Clone, PartialEq)] pub enum CliNonceError { InvalidAccountOwner, InvalidAccountData, InvalidHash, InvalidAuthority, InvalidState, } pub const NONCE_ARG: ArgConstant<'static> = ArgConstant { name: "nonce", long: "nonce", help: "Provide the nonce account to use when creating a nonced \n\ transaction. Nonced transactions are useful when a transaction \n\ requires a lengthy signing process. Learn more about nonced \n\ transactions at https://docs.solana.com/offline-signing/durable-nonce", }; pub const NONCE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant { name: "nonce_authority", long: "nonce-authority", help: "Provide the nonce authority keypair to use when signing a nonced transaction", }; pub trait NonceSubCommands { fn nonce_subcommands(self) -> Self; } pub fn nonce_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(NONCE_ARG.name) .long(NONCE_ARG.long) .takes_value(true) .value_name("PUBKEY") .requires(BLOCKHASH_ARG.name) .validator(is_pubkey) .help(NONCE_ARG.help) } pub fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name(NONCE_AUTHORITY_ARG.name) .long(NONCE_AUTHORITY_ARG.long) .takes_value(true) .value_name("KEYPAIR or PUBKEY") .validator(is_pubkey_or_keypair_or_ask_keyword) .help(NONCE_AUTHORITY_ARG.help) } impl NonceSubCommands for App<'_, '_> { fn nonce_subcommands(self) -> Self { self.subcommand( SubCommand::with_name("authorize-nonce-account") .about("Assign account authority to a new entity") .arg( Arg::with_name("nonce_account_keypair") .index(1) .value_name("NONCE_ACCOUNT") .takes_value(true) .required(true) .validator(is_pubkey_or_keypair) .help("Address of the nonce account"), ) .arg( Arg::with_name("new_authority") .index(2) .value_name("NEW_AUTHORITY_PUBKEY") .takes_value(true) .required(true) .validator(is_pubkey_or_keypair) .help("Account to be granted authority of the nonce account"), ) .arg( Arg::with_name("seed") .long("seed") .value_name("SEED STRING") .takes_value(true) .help("Seed for address generation; if specified, the resulting account will be at a derived address of the NONCE_ACCOUNT pubkey") ) .arg(nonce_authority_arg()), ) .subcommand( SubCommand::with_name("create-nonce-account") .about("Create a nonce account") .arg( Arg::with_name("nonce_account_keypair") .index(1) .value_name("NONCE ACCOUNT") .takes_value(true) .required(true) .validator(is_pubkey_or_keypair) .help("Keypair of the nonce account to fund"), ) .arg( Arg::with_name("amount") .index(2) .value_name("AMOUNT") .takes_value(true) .required(true) .validator(is_amount) .help("The amount to load the nonce account with (default unit SOL)"), ) .arg( Arg::with_name("unit") .index(3) .value_name("UNIT") .takes_value(true) .possible_values(&["SOL", "lamports"]) .help("Specify unit to use for request"), ) .arg( Arg::with_name(NONCE_AUTHORITY_ARG.name) .long(NONCE_AUTHORITY_ARG.long) .takes_value(true) .value_name("BASE58_PUBKEY") .validator(is_pubkey_or_keypair) .help("Assign noncing authority to another entity"), ), ) .subcommand( SubCommand::with_name("nonce") .about("Get the current nonce value") .alias("get-nonce") .arg( Arg::with_name("nonce_account_pubkey") .index(1) .value_name("NONCE ACCOUNT") .takes_value(true) .required(true) .validator(is_pubkey_or_keypair) .help("Address of the nonce account to display"), ), ) .subcommand( SubCommand::with_name("new-nonce") .about("Generate a new nonce, rendering the existing nonce useless") .arg( Arg::with_name("nonce_account_keypair") .index(1) .value_name("NONCE ACCOUNT") .takes_value(true) .required(true) .validator(is_pubkey_or_keypair) .help("Address of the nonce account"), ) .arg(nonce_authority_arg()), ) .subcommand( SubCommand::with_name("nonce-account") .about("Show the contents of a nonce account") .alias("show-nonce-account") .arg( Arg::with_name("nonce_account_pubkey") .index(1) .value_name("NONCE ACCOUNT") .takes_value(true) .required(true) .validator(is_pubkey_or_keypair) .help("Address of the nonce account to display"), ) .arg( Arg::with_name("lamports") .long("lamports") .takes_value(false) .help("Display balance in lamports instead of SOL"), ), ) .subcommand( SubCommand::with_name("withdraw-from-nonce-account") .about("Withdraw lamports from the nonce account") .arg( Arg::with_name("nonce_account_keypair") .index(1) .value_name("NONCE ACCOUNT") .takes_value(true) .required(true) .validator(is_keypair_or_ask_keyword) .help("Nonce account from to withdraw from"), ) .arg( Arg::with_name("destination_account_pubkey") .index(2) .value_name("DESTINATION ACCOUNT") .takes_value(true) .required(true) .validator(is_pubkey_or_keypair) .help("The account to which the lamports should be transferred"), ) .arg( Arg::with_name("amount") .index(3) .value_name("AMOUNT") .takes_value(true) .required(true) .validator(is_amount) .help("The amount to withdraw from the nonce account (default unit SOL)"), ) .arg( Arg::with_name("unit") .index(4) .value_name("UNIT") .takes_value(true) .possible_values(&["SOL", "lamports"]) .help("Specify unit to use for request"), ) .arg(nonce_authority_arg()), ) } } pub fn parse_authorize_nonce_account(matches: &ArgMatches<'_>) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); let new_authority = pubkey_of(matches, "new_authority").unwrap(); let nonce_authority = SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?; Ok(CliCommandInfo { command: CliCommand::AuthorizeNonceAccount { nonce_account, nonce_authority, new_authority, }, require_keypair: true, }) } pub fn parse_nonce_create_account(matches: &ArgMatches<'_>) -> Result { let nonce_account = keypair_of(matches, "nonce_account_keypair").unwrap(); let seed = matches.value_of("seed").map(|s| s.to_string()); let lamports = required_lamports_from(matches, "amount", "unit")?; let nonce_authority = pubkey_of(matches, NONCE_AUTHORITY_ARG.name); Ok(CliCommandInfo { command: CliCommand::CreateNonceAccount { nonce_account: nonce_account.into(), seed, nonce_authority, lamports, }, require_keypair: true, }) } pub fn parse_get_nonce(matches: &ArgMatches<'_>) -> Result { let nonce_account_pubkey = pubkey_of(matches, "nonce_account_pubkey").unwrap(); Ok(CliCommandInfo { command: CliCommand::GetNonce(nonce_account_pubkey), require_keypair: false, }) } pub fn parse_new_nonce(matches: &ArgMatches<'_>) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); let nonce_authority = SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?; Ok(CliCommandInfo { command: CliCommand::NewNonce { nonce_account, nonce_authority, }, require_keypair: true, }) } pub fn parse_show_nonce_account(matches: &ArgMatches<'_>) -> Result { let nonce_account_pubkey = pubkey_of(matches, "nonce_account_pubkey").unwrap(); let use_lamports_unit = matches.is_present("lamports"); Ok(CliCommandInfo { command: CliCommand::ShowNonceAccount { nonce_account_pubkey, use_lamports_unit, }, require_keypair: false, }) } pub fn parse_withdraw_from_nonce_account( matches: &ArgMatches<'_>, ) -> Result { let nonce_account = pubkey_of(matches, "nonce_account_keypair").unwrap(); let destination_account_pubkey = pubkey_of(matches, "destination_account_pubkey").unwrap(); let lamports = required_lamports_from(matches, "amount", "unit")?; let nonce_authority = SigningAuthority::new_from_matches(&matches, NONCE_AUTHORITY_ARG.name, None)?; Ok(CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account, nonce_authority, destination_account_pubkey, lamports, }, require_keypair: true, }) } /// Check if a nonce account is initialized with the given authority and hash pub fn check_nonce_account( nonce_account: &Account, nonce_authority: &Pubkey, nonce_hash: &Hash, ) -> Result<(), Box> { if nonce_account.owner != system_program::ID { return Err(CliError::InvalidNonce(CliNonceError::InvalidAccountOwner).into()); } let nonce_state: NonceState = nonce_account .state() .map_err(|_| Box::new(CliError::InvalidNonce(CliNonceError::InvalidAccountData)))?; match nonce_state { NonceState::Initialized(meta, hash) => { if &hash != nonce_hash { Err(CliError::InvalidNonce(CliNonceError::InvalidHash).into()) } else if nonce_authority != &meta.nonce_authority { Err(CliError::InvalidNonce(CliNonceError::InvalidAuthority).into()) } else { Ok(()) } } NonceState::Uninitialized => { Err(CliError::InvalidNonce(CliNonceError::InvalidState).into()) } } } pub fn process_authorize_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, nonce_authority: Option<&SigningAuthority>, new_authority: &Pubkey, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let nonce_authority = nonce_authority .map(|a| a.keypair()) .unwrap_or(&config.keypair); let ix = authorize_nonce_account(nonce_account, &nonce_authority.pubkey(), new_authority); let mut tx = Transaction::new_signed_with_payer( vec![ix], Some(&config.keypair.pubkey()), &[&config.keypair, nonce_authority], recent_blockhash, ); check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]); log_instruction_custom_error::(result) } pub fn process_create_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Keypair, seed: Option, nonce_authority: Option, lamports: u64, ) -> ProcessResult { let nonce_account_pubkey = nonce_account.pubkey(); let nonce_account_address = if let Some(seed) = seed.clone() { create_address_with_seed(&nonce_account_pubkey, &seed, &system_program::id())? } else { nonce_account_pubkey }; check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), (&nonce_account_address, "nonce_account".to_string()), )?; if let Ok(nonce_account) = rpc_client.get_account(&nonce_account_address) { let err_msg = if nonce_account.owner == system_program::id() && StateMut::::state(&nonce_account).is_ok() { format!("Nonce account {} already exists", nonce_account_address) } else { format!( "Account {} already exists and is not a nonce account", nonce_account_address ) }; return Err(CliError::BadParameter(err_msg).into()); } let minimum_balance = rpc_client.get_minimum_balance_for_rent_exemption(NonceState::size())?; if lamports < minimum_balance { return Err(CliError::BadParameter(format!( "need at least {} lamports for nonce account to be rent exempt, provided lamports: {}", minimum_balance, lamports )) .into()); } let nonce_authority = nonce_authority.unwrap_or_else(|| config.keypair.pubkey()); let ixs = if let Some(seed) = seed { create_nonce_account_with_seed( &config.keypair.pubkey(), // from &nonce_account_address, // to &nonce_account_pubkey, // base &seed, // seed &nonce_authority, lamports, ) } else { create_nonce_account( &config.keypair.pubkey(), &nonce_account_pubkey, &nonce_authority, lamports, ) }; let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let signers = if nonce_account_pubkey != config.keypair.pubkey() { vec![&config.keypair, nonce_account] // both must sign if `from` and `to` differ } else { vec![&config.keypair] // when stake_account == config.keypair and there's a seed, we only need one signature }; let mut tx = Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), &signers, recent_blockhash, ); check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &signers); log_instruction_custom_error::(result) } pub fn process_get_nonce(rpc_client: &RpcClient, nonce_account_pubkey: &Pubkey) -> ProcessResult { let nonce_account = rpc_client.get_account(nonce_account_pubkey)?; if nonce_account.owner != system_program::id() { return Err(CliError::RpcRequestError(format!( "{:?} is not a nonce account", nonce_account_pubkey )) .into()); } match nonce_account.state() { Ok(NonceState::Uninitialized) => Ok("Nonce account is uninitialized".to_string()), Ok(NonceState::Initialized(_, hash)) => Ok(format!("{:?}", hash)), Err(err) => Err(CliError::RpcRequestError(format!( "Account data could not be deserialized to nonce state: {:?}", err )) .into()), } } pub fn process_new_nonce( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, nonce_authority: Option<&SigningAuthority>, ) -> ProcessResult { check_unique_pubkeys( (&config.keypair.pubkey(), "cli keypair".to_string()), (&nonce_account, "nonce_account_pubkey".to_string()), )?; if rpc_client.get_account(&nonce_account).is_err() { return Err(CliError::BadParameter( "Unable to create new nonce, no nonce account found".to_string(), ) .into()); } let nonce_authority = nonce_authority .map(|a| a.keypair()) .unwrap_or(&config.keypair); let ix = advance_nonce_account(&nonce_account, &nonce_authority.pubkey()); let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let mut tx = Transaction::new_signed_with_payer( vec![ix], Some(&config.keypair.pubkey()), &[&config.keypair, nonce_authority], recent_blockhash, ); check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]); log_instruction_custom_error::(result) } pub fn process_show_nonce_account( rpc_client: &RpcClient, nonce_account_pubkey: &Pubkey, use_lamports_unit: bool, ) -> ProcessResult { let nonce_account = rpc_client.get_account(nonce_account_pubkey)?; if nonce_account.owner != system_program::id() { return Err(CliError::RpcRequestError(format!( "{:?} is not a nonce account", nonce_account_pubkey )) .into()); } let print_account = |data: Option<(Meta, Hash)>| { println!( "Balance: {}", build_balance_message(nonce_account.lamports, use_lamports_unit, true) ); println!( "Minimum Balance Required: {}", build_balance_message( rpc_client.get_minimum_balance_for_rent_exemption(NonceState::size())?, use_lamports_unit, true ) ); match data { Some((meta, hash)) => { println!("Nonce: {}", hash); println!("Authority: {}", meta.nonce_authority); } None => { println!("Nonce: uninitialized"); println!("Authority: uninitialized"); } } Ok("".to_string()) }; match nonce_account.state() { Ok(NonceState::Uninitialized) => print_account(None), Ok(NonceState::Initialized(meta, hash)) => print_account(Some((meta, hash))), Err(err) => Err(CliError::RpcRequestError(format!( "Account data could not be deserialized to nonce state: {:?}", err )) .into()), } } pub fn process_withdraw_from_nonce_account( rpc_client: &RpcClient, config: &CliConfig, nonce_account: &Pubkey, nonce_authority: Option<&SigningAuthority>, destination_account_pubkey: &Pubkey, lamports: u64, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let nonce_authority = nonce_authority .map(|a| a.keypair()) .unwrap_or(&config.keypair); let ix = withdraw_nonce_account( nonce_account, &nonce_authority.pubkey(), destination_account_pubkey, lamports, ); let mut tx = Transaction::new_signed_with_payer( vec![ix], Some(&config.keypair.pubkey()), &[&config.keypair, nonce_authority], recent_blockhash, ); check_account_for_fee( rpc_client, &config.keypair.pubkey(), &fee_calculator, &tx.message, )?; let result = rpc_client.send_and_confirm_transaction(&mut tx, &[&config.keypair, nonce_authority]); log_instruction_custom_error::(result) } #[cfg(test)] mod tests { use super::*; use crate::cli::{app, parse_command}; use solana_sdk::{ account::Account, hash::hash, nonce_state::{Meta as NonceMeta, NonceState}, signature::{read_keypair_file, write_keypair}, system_program, }; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { let tmp_file = NamedTempFile::new().unwrap(); (String::from(tmp_file.path().to_str().unwrap()), tmp_file) } #[test] fn test_parse_command() { let test_commands = app("test", "desc", "version"); let (keypair_file, mut tmp_file) = make_tmp_file(); let nonce_account_keypair = Keypair::new(); write_keypair(&nonce_account_keypair, tmp_file.as_file_mut()).unwrap(); let nonce_account_pubkey = nonce_account_keypair.pubkey(); let nonce_account_string = nonce_account_pubkey.to_string(); let (authority_keypair_file, mut tmp_file2) = make_tmp_file(); let nonce_authority_keypair = Keypair::new(); write_keypair(&nonce_authority_keypair, tmp_file2.as_file_mut()).unwrap(); // Test AuthorizeNonceAccount Subcommand let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "authorize-nonce-account", &keypair_file, &Pubkey::default().to_string(), ]); assert_eq!( parse_command(&test_authorize_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::AuthorizeNonceAccount { nonce_account: nonce_account_pubkey, nonce_authority: None, new_authority: Pubkey::default(), }, require_keypair: true, } ); // Test AuthorizeNonceAccount Subcommand with authority let test_authorize_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "authorize-nonce-account", &keypair_file, &Pubkey::default().to_string(), "--nonce-authority", &authority_keypair_file, ]); assert_eq!( parse_command(&test_authorize_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::AuthorizeNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), nonce_authority: Some( read_keypair_file(&authority_keypair_file).unwrap().into() ), new_authority: Pubkey::default(), }, require_keypair: true, } ); // Test CreateNonceAccount SubCommand let test_create_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "create-nonce-account", &keypair_file, "50", "lamports", ]); assert_eq!( parse_command(&test_create_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::CreateNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().into(), seed: None, nonce_authority: None, lamports: 50, }, require_keypair: true } ); // Test CreateNonceAccount SubCommand with authority let test_create_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "create-nonce-account", &keypair_file, "50", "lamports", "--nonce-authority", &authority_keypair_file, ]); assert_eq!( parse_command(&test_create_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::CreateNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().into(), seed: None, nonce_authority: Some( read_keypair_file(&authority_keypair_file).unwrap().pubkey() ), lamports: 50, }, require_keypair: true } ); // Test GetNonce Subcommand let test_get_nonce = test_commands.clone().get_matches_from(vec![ "test", "get-nonce", &nonce_account_string, ]); assert_eq!( parse_command(&test_get_nonce).unwrap(), CliCommandInfo { command: CliCommand::GetNonce(nonce_account_keypair.pubkey(),), require_keypair: false } ); // Test NewNonce SubCommand let test_new_nonce = test_commands .clone() .get_matches_from(vec!["test", "new-nonce", &keypair_file]); let nonce_account = read_keypair_file(&keypair_file).unwrap(); assert_eq!( parse_command(&test_new_nonce).unwrap(), CliCommandInfo { command: CliCommand::NewNonce { nonce_account: nonce_account.pubkey(), nonce_authority: None, }, require_keypair: true } ); // Test NewNonce SubCommand with authority let test_new_nonce = test_commands.clone().get_matches_from(vec![ "test", "new-nonce", &keypair_file, "--nonce-authority", &authority_keypair_file, ]); let nonce_account = read_keypair_file(&keypair_file).unwrap(); assert_eq!( parse_command(&test_new_nonce).unwrap(), CliCommandInfo { command: CliCommand::NewNonce { nonce_account: nonce_account.pubkey(), nonce_authority: Some( read_keypair_file(&authority_keypair_file).unwrap().into() ), }, require_keypair: true } ); // Test ShowNonceAccount Subcommand let test_show_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "nonce-account", &nonce_account_string, ]); assert_eq!( parse_command(&test_show_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::ShowNonceAccount { nonce_account_pubkey: nonce_account_keypair.pubkey(), use_lamports_unit: false, }, require_keypair: false } ); // Test WithdrawFromNonceAccount Subcommand let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "withdraw-from-nonce-account", &keypair_file, &nonce_account_string, "42", "lamports", ]); assert_eq!( parse_command(&test_withdraw_from_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), nonce_authority: None, destination_account_pubkey: nonce_account_pubkey, lamports: 42 }, require_keypair: true } ); let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "withdraw-from-nonce-account", &keypair_file, &nonce_account_string, "42", "SOL", ]); assert_eq!( parse_command(&test_withdraw_from_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), nonce_authority: None, destination_account_pubkey: nonce_account_pubkey, lamports: 42000000000 }, require_keypair: true } ); // Test WithdrawFromNonceAccount Subcommand with authority let test_withdraw_from_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "withdraw-from-nonce-account", &keypair_file, &nonce_account_string, "42", "lamports", "--nonce-authority", &authority_keypair_file, ]); assert_eq!( parse_command(&test_withdraw_from_nonce_account).unwrap(), CliCommandInfo { command: CliCommand::WithdrawFromNonceAccount { nonce_account: read_keypair_file(&keypair_file).unwrap().pubkey(), nonce_authority: Some( read_keypair_file(&authority_keypair_file).unwrap().into() ), destination_account_pubkey: nonce_account_pubkey, lamports: 42 }, require_keypair: true } ); } #[test] fn test_check_nonce_account() { let blockhash = Hash::default(); let nonce_pubkey = Pubkey::new_rand(); let valid = Account::new_data( 1, &NonceState::Initialized(NonceMeta::new(&nonce_pubkey), blockhash), &system_program::ID, ); assert!(check_nonce_account(&valid.unwrap(), &nonce_pubkey, &blockhash).is_ok()); let invalid_owner = Account::new_data( 1, &NonceState::Initialized(NonceMeta::new(&nonce_pubkey), blockhash), &Pubkey::new(&[1u8; 32]), ); assert_eq!( check_nonce_account(&invalid_owner.unwrap(), &nonce_pubkey, &blockhash), Err(Box::new(CliError::InvalidNonce( CliNonceError::InvalidAccountOwner ))), ); let invalid_data = Account::new_data(1, &"invalid", &system_program::ID); assert_eq!( check_nonce_account(&invalid_data.unwrap(), &nonce_pubkey, &blockhash), Err(Box::new(CliError::InvalidNonce( CliNonceError::InvalidAccountData ))), ); let invalid_hash = Account::new_data( 1, &NonceState::Initialized(NonceMeta::new(&nonce_pubkey), hash(b"invalid")), &system_program::ID, ); assert_eq!( check_nonce_account(&invalid_hash.unwrap(), &nonce_pubkey, &blockhash), Err(Box::new(CliError::InvalidNonce(CliNonceError::InvalidHash))), ); let invalid_authority = Account::new_data( 1, &NonceState::Initialized(NonceMeta::new(&Pubkey::new_rand()), blockhash), &system_program::ID, ); assert_eq!( check_nonce_account(&invalid_authority.unwrap(), &nonce_pubkey, &blockhash), Err(Box::new(CliError::InvalidNonce( CliNonceError::InvalidAuthority ))), ); let invalid_state = Account::new_data(1, &NonceState::Uninitialized, &system_program::ID); assert_eq!( check_nonce_account(&invalid_state.unwrap(), &nonce_pubkey, &blockhash), Err(Box::new(CliError::InvalidNonce( CliNonceError::InvalidState ))), ); } }