diff --git a/cli/src/cli.rs b/cli/src/cli.rs index eef2bce583..69b8a0ecfd 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -115,6 +115,11 @@ pub enum CliCommand { use_lamports_unit: bool, }, // Nonce commands + AuthorizeNonceAccount { + nonce_account: Pubkey, + nonce_authority: KeypairEq, + new_authority: Pubkey, + }, CreateNonceAccount { nonce_account: KeypairEq, nonce_authority: Pubkey, @@ -347,6 +352,7 @@ pub fn parse_command(matches: &ArgMatches<'_>) -> Result parse_show_validators(matches), // Nonce Commands + ("authorize-nonce-account", Some(matches)) => parse_authorize_nonce_account(matches), ("create-nonce-account", Some(matches)) => parse_nonce_create_account(matches), ("get-nonce", Some(matches)) => parse_get_nonce(matches), ("new-nonce", Some(matches)) => parse_new_nonce(matches), @@ -1139,6 +1145,18 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Nonce Commands + // Assign authority to nonce account + CliCommand::AuthorizeNonceAccount { + nonce_account, + nonce_authority, + new_authority, + } => process_authorize_nonce_account( + &rpc_client, + config, + nonce_account, + nonce_authority, + new_authority, + ), // Create nonce account CliCommand::CreateNonceAccount { nonce_account, diff --git a/cli/src/nonce.rs b/cli/src/nonce.rs index c73868e443..2715bc6ff0 100644 --- a/cli/src/nonce.rs +++ b/cli/src/nonce.rs @@ -9,7 +9,7 @@ use solana_client::rpc_client::RpcClient; use solana_sdk::{ account_utils::State, hash::Hash, - nonce_instruction::{create_nonce_account, nonce, withdraw, NonceError}, + nonce_instruction::{authorize, create_nonce_account, nonce, withdraw, NonceError}, nonce_program, nonce_state::NonceState, pubkey::Pubkey, @@ -34,6 +34,29 @@ fn nonce_authority_arg<'a, 'b>() -> Arg<'a, 'b> { 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(nonce_authority_arg()), + ) + .subcommand( SubCommand::with_name("create-nonce-account") .about("Create a nonce account") .arg( @@ -165,6 +188,21 @@ fn resolve_nonce_authority(matches: &ArgMatches<'_>) -> Keypair { .unwrap_or_else(|| keypair_of(matches, "nonce_account_keypair").unwrap()) } +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 = resolve_nonce_authority(matches); + + Ok(CliCommandInfo { + command: CliCommand::AuthorizeNonceAccount { + nonce_account, + nonce_authority: nonce_authority.into(), + 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 lamports = required_lamports_from(matches, "amount", "unit")?; @@ -235,6 +273,33 @@ pub fn parse_withdraw_from_nonce_account( }) } +pub fn process_authorize_nonce_account( + rpc_client: &RpcClient, + config: &CliConfig, + nonce_account: &Pubkey, + nonce_authority: &Keypair, + new_authority: &Pubkey, +) -> ProcessResult { + let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; + + let ix = authorize(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, @@ -441,6 +506,50 @@ mod tests { 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: read_keypair_file(&keypair_file).unwrap().into(), + 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: 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", @@ -462,9 +571,6 @@ mod tests { ); // Test CreateNonceAccount SubCommand with authority - 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(); let test_create_nonce_account = test_commands.clone().get_matches_from(vec![ "test", "create-nonce-account", diff --git a/cli/tests/nonce.rs b/cli/tests/nonce.rs index 7721b66516..7c5c2701c8 100644 --- a/cli/tests/nonce.rs +++ b/cli/tests/nonce.rs @@ -174,4 +174,47 @@ fn full_battery_tests( use_lamports_unit: true, }; process_command(&config_payer).unwrap(); + + // Set new authority + let new_authority = Keypair::new(); + let (new_authority_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&new_authority, tmp_file.as_file_mut()).unwrap(); + config_payer.command = CliCommand::AuthorizeNonceAccount { + nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_authority: read_keypair_file(&authority_keypair_file).unwrap().into(), + new_authority: read_keypair_file(&new_authority_keypair_file) + .unwrap() + .pubkey(), + }; + process_command(&config_payer).unwrap(); + + // Old authority fails now + config_payer.command = CliCommand::NewNonce { + nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_authority: read_keypair_file(&authority_keypair_file).unwrap().into(), + }; + process_command(&config_payer).unwrap_err(); + + // New authority can advance nonce + config_payer.command = CliCommand::NewNonce { + nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_authority: read_keypair_file(&new_authority_keypair_file) + .unwrap() + .into(), + }; + process_command(&config_payer).unwrap(); + + // New authority can withdraw from nonce account + config_payer.command = CliCommand::WithdrawFromNonceAccount { + nonce_account: read_keypair_file(&nonce_keypair_file).unwrap().pubkey(), + nonce_authority: read_keypair_file(&new_authority_keypair_file) + .unwrap() + .into(), + destination_account_pubkey: payee_pubkey, + lamports: 100, + }; + process_command(&config_payer).unwrap(); + check_balance(1000, &rpc_client, &config_payer.keypair.pubkey()); + check_balance(800, &rpc_client, &config_nonce.keypair.pubkey()); + check_balance(200, &rpc_client, &payee_pubkey); }