From 8ffccfbaff982cdf7e84e1429e5da14cdf80e48d Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Wed, 15 Jan 2020 14:32:06 -0700 Subject: [PATCH] CLI: Plumb stake authorities throughout (#7822) automerge --- cli/src/cli.rs | 56 +++++-- cli/src/stake.rs | 353 ++++++++++++++++++++++++++++++++++++--------- cli/tests/stake.rs | 88 ++++++++++- 3 files changed, 417 insertions(+), 80 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 28aef01e58..e81908e98c 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -169,6 +169,7 @@ pub enum CliCommand { }, DeactivateStake { stake_account_pubkey: Pubkey, + stake_authority: Option, sign_only: bool, signers: Option>, blockhash: Option, @@ -178,6 +179,7 @@ pub enum CliCommand { DelegateStake { stake_account_pubkey: Pubkey, vote_account_pubkey: Pubkey, + stake_authority: Option, force: bool, sign_only: bool, signers: Option>, @@ -193,8 +195,18 @@ pub enum CliCommand { pubkey: Pubkey, use_lamports_unit: bool, }, - StakeAuthorize(Pubkey, Pubkey, StakeAuthorize), - WithdrawStake(Pubkey, Pubkey, u64), + StakeAuthorize { + stake_account_pubkey: Pubkey, + new_authorized_pubkey: Pubkey, + stake_authorize: StakeAuthorize, + authority: Option, + }, + WithdrawStake { + stake_account_pubkey: Pubkey, + destination_account_pubkey: Pubkey, + lamports: u64, + withdraw_authority: Option, + }, // Storage Commands CreateStorageAccount { account_owner: Pubkey, @@ -1279,6 +1291,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Deactivate stake account CliCommand::DeactivateStake { stake_account_pubkey, + ref stake_authority, sign_only, ref signers, blockhash, @@ -1288,6 +1301,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, &stake_account_pubkey, + stake_authority.as_deref(), *sign_only, signers, *blockhash, @@ -1297,6 +1311,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + ref stake_authority, force, sign_only, ref signers, @@ -1308,6 +1323,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { config, &stake_account_pubkey, &vote_account_pubkey, + stake_authority.as_deref(), *force, *sign_only, signers, @@ -1335,27 +1351,33 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::ShowStakeHistory { use_lamports_unit } => { process_show_stake_history(&rpc_client, config, *use_lamports_unit) } - CliCommand::StakeAuthorize( + CliCommand::StakeAuthorize { stake_account_pubkey, new_authorized_pubkey, stake_authorize, - ) => process_stake_authorize( + ref authority, + } => process_stake_authorize( &rpc_client, config, &stake_account_pubkey, &new_authorized_pubkey, *stake_authorize, + authority.as_deref(), ), - CliCommand::WithdrawStake(stake_account_pubkey, destination_account_pubkey, lamports) => { - process_withdraw_stake( - &rpc_client, - config, - &stake_account_pubkey, - &destination_account_pubkey, - *lamports, - ) - } + CliCommand::WithdrawStake { + stake_account_pubkey, + destination_account_pubkey, + lamports, + ref withdraw_authority, + } => process_withdraw_stake( + &rpc_client, + config, + &stake_account_pubkey, + &destination_account_pubkey, + *lamports, + withdraw_authority.as_deref(), + ), // Storage Commands @@ -2593,13 +2615,19 @@ mod tests { let stake_pubkey = Pubkey::new_rand(); let to_pubkey = Pubkey::new_rand(); - config.command = CliCommand::WithdrawStake(stake_pubkey, to_pubkey, 100); + config.command = CliCommand::WithdrawStake { + stake_account_pubkey: stake_pubkey, + destination_account_pubkey: to_pubkey, + lamports: 100, + withdraw_authority: None, + }; let signature = process_command(&config); assert_eq!(signature.unwrap(), SIGNATURE.to_string()); let stake_pubkey = Pubkey::new_rand(); config.command = CliCommand::DeactivateStake { stake_account_pubkey: stake_pubkey, + stake_authority: None, sign_only: false, signers: None, blockhash: None, diff --git a/cli/src/stake.rs b/cli/src/stake.rs index c63fb73765..f9312cc99d 100644 --- a/cli/src/stake.rs +++ b/cli/src/stake.rs @@ -9,7 +9,7 @@ use crate::{ }; use clap::{App, Arg, ArgMatches, SubCommand}; use console::style; -use solana_clap_utils::{input_parsers::*, input_validators::*}; +use solana_clap_utils::{input_parsers::*, input_validators::*, ArgConstant}; use solana_client::rpc_client::RpcClient; use solana_sdk::signature::{Keypair, Signature}; use solana_sdk::{ @@ -32,6 +32,36 @@ use solana_stake_program::{ use solana_vote_program::vote_state::VoteState; use std::ops::Deref; +pub const STAKE_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant { + name: "stake_authority", + long: "stake-authority", + help: "Public key of authorized staker (defaults to cli config pubkey)", +}; + +pub const WITHDRAW_AUTHORITY_ARG: ArgConstant<'static> = ArgConstant { + name: "withdraw_authority", + long: "withdraw-authority", + help: "Public key of authorized withdrawer (defaults to cli config pubkey)", +}; + +fn stake_authority_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name(STAKE_AUTHORITY_ARG.name) + .long(STAKE_AUTHORITY_ARG.long) + .takes_value(true) + .value_name("KEYPAIR") + .validator(is_keypair_or_ask_keyword) + .help(STAKE_AUTHORITY_ARG.help) +} + +fn withdraw_authority_arg<'a, 'b>() -> Arg<'a, 'b> { + Arg::with_name(WITHDRAW_AUTHORITY_ARG.name) + .long(WITHDRAW_AUTHORITY_ARG.long) + .takes_value(true) + .value_name("KEYPAIR") + .validator(is_keypair_or_ask_keyword) + .help(WITHDRAW_AUTHORITY_ARG.help) +} + pub trait StakeSubCommands { fn stake_subcommands(self) -> Self; } @@ -98,20 +128,20 @@ impl StakeSubCommands for App<'_, '_> { .help("The date and time at which this account will be available for withdrawal") ) .arg( - Arg::with_name("authorized_staker") - .long("authorized-staker") + Arg::with_name(STAKE_AUTHORITY_ARG.name) + .long(STAKE_AUTHORITY_ARG.long) .value_name("PUBKEY") .takes_value(true) .validator(is_pubkey_or_keypair) - .help("Public key of authorized staker (defaults to cli config pubkey)") + .help(STAKE_AUTHORITY_ARG.help) ) .arg( - Arg::with_name("authorized_withdrawer") - .long("authorized-withdrawer") + Arg::with_name(WITHDRAW_AUTHORITY_ARG.name) + .long(WITHDRAW_AUTHORITY_ARG.long) .value_name("PUBKEY") .takes_value(true) .validator(is_pubkey_or_keypair) - .help("Public key of the authorized withdrawer (defaults to cli config pubkey)") + .help(WITHDRAW_AUTHORITY_ARG.help) ) ) .subcommand( @@ -142,6 +172,7 @@ impl StakeSubCommands for App<'_, '_> { .validator(is_pubkey_or_keypair) .help("The vote account to which the stake will be delegated") ) + .arg(stake_authority_arg()) .arg( Arg::with_name("sign_only") .long("sign-only") @@ -204,6 +235,7 @@ impl StakeSubCommands for App<'_, '_> { .validator(is_pubkey_or_keypair) .help("New authorized staker") ) + .arg(stake_authority_arg()) ) .subcommand( SubCommand::with_name("stake-authorize-withdrawer") @@ -226,6 +258,7 @@ impl StakeSubCommands for App<'_, '_> { .validator(is_pubkey_or_keypair) .help("New authorized withdrawer") ) + .arg(withdraw_authority_arg()) ) .subcommand( SubCommand::with_name("deactivate-stake") @@ -238,6 +271,7 @@ impl StakeSubCommands for App<'_, '_> { .required(true) .help("Stake account to be deactivated.") ) + .arg(stake_authority_arg()) .arg( Arg::with_name("sign_only") .long("sign-only") @@ -317,6 +351,7 @@ impl StakeSubCommands for App<'_, '_> { .possible_values(&["SOL", "lamports"]) .help("Specify unit to use for request") ) + .arg(withdraw_authority_arg()) ) .subcommand( SubCommand::with_name("redeem-vote-credits") @@ -378,8 +413,8 @@ pub fn parse_stake_create_account(matches: &ArgMatches<'_>) -> Result) -> Result) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); let vote_account_pubkey = pubkey_of(matches, "vote_account_pubkey").unwrap(); + let stake_authority = if matches.is_present(STAKE_AUTHORITY_ARG.name) { + let authority = keypair_of(&matches, STAKE_AUTHORITY_ARG.name) + .ok_or_else(|| CliError::BadParameter("Invalid keypair for stake-authority".into()))?; + Some(authority.into()) + } else { + None + }; let force = matches.is_present("force"); let sign_only = matches.is_present("sign_only"); let signers = pubkeys_sigs_of(&matches, "signer"); @@ -420,6 +462,7 @@ pub fn parse_stake_delegate_stake(matches: &ArgMatches<'_>) -> Result Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); - let authorized_pubkey = pubkey_of(matches, "authorized_pubkey").unwrap(); + let new_authorized_pubkey = pubkey_of(matches, "authorized_pubkey").unwrap(); + let authority_flag = match stake_authorize { + StakeAuthorize::Staker => STAKE_AUTHORITY_ARG.name, + StakeAuthorize::Withdrawer => WITHDRAW_AUTHORITY_ARG.name, + }; + let authority = if matches.is_present(authority_flag) { + let authority = keypair_of(&matches, authority_flag).ok_or_else(|| { + CliError::BadParameter(format!("Invalid keypair for {}", authority_flag)) + })?; + Some(authority.into()) + } else { + None + }; Ok(CliCommandInfo { - command: CliCommand::StakeAuthorize( + command: CliCommand::StakeAuthorize { stake_account_pubkey, - authorized_pubkey, + new_authorized_pubkey, stake_authorize, - ), + authority, + }, require_keypair: true, }) } @@ -460,6 +516,13 @@ pub fn parse_redeem_vote_credits(matches: &ArgMatches<'_>) -> Result) -> Result { let stake_account_pubkey = pubkey_of(matches, "stake_account_pubkey").unwrap(); + let stake_authority = if matches.is_present(STAKE_AUTHORITY_ARG.name) { + let authority = keypair_of(&matches, STAKE_AUTHORITY_ARG.name) + .ok_or_else(|| CliError::BadParameter("Invalid keypair for stake-authority".into()))?; + Some(authority.into()) + } else { + None + }; let sign_only = matches.is_present("sign_only"); let signers = pubkeys_sigs_of(&matches, "signer"); let blockhash = value_of(matches, "blockhash"); @@ -476,6 +539,7 @@ pub fn parse_stake_deactivate_stake(matches: &ArgMatches<'_>) -> Result) -> Result, ) -> ProcessResult { check_unique_pubkeys( (stake_account_pubkey, "stake_account_pubkey".to_string()), (authorized_pubkey, "new_authorized_pubkey".to_string()), )?; + let authority = authority.unwrap_or(&config.keypair); let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; let ixs = vec![stake_instruction::authorize( - stake_account_pubkey, // stake account to update - &config.keypair.pubkey(), // currently authorized - authorized_pubkey, // new stake signer - stake_authorize, // stake or withdraw + stake_account_pubkey, // stake account to update + &authority.pubkey(), // currently authorized + authorized_pubkey, // new stake signer + stake_authorize, // stake or withdraw )]; let mut tx = Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair], + &[&config.keypair, authority], recent_blockhash, ); check_account_for_fee( @@ -652,6 +727,7 @@ pub fn process_deactivate_stake_account( rpc_client: &RpcClient, config: &CliConfig, stake_account_pubkey: &Pubkey, + stake_authority: Option<&Keypair>, sign_only: bool, signers: &Option>, blockhash: Option, @@ -660,16 +736,17 @@ pub fn process_deactivate_stake_account( ) -> ProcessResult { let (recent_blockhash, fee_calculator) = get_blockhash_fee_calculator(rpc_client, sign_only, blockhash)?; + let stake_authority = stake_authority.unwrap_or(&config.keypair); let ixs = vec![stake_instruction::deactivate_stake( stake_account_pubkey, - &config.keypair.pubkey(), + &stake_authority.pubkey(), )]; let mut tx = if let Some(nonce_account) = &nonce_account { let nonce_authority: &Keypair = nonce_authority.unwrap_or(&config.keypair); Transaction::new_signed_with_nonce( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority], + &[&config.keypair, nonce_authority, stake_authority], nonce_account, &nonce_authority.pubkey(), recent_blockhash, @@ -678,7 +755,7 @@ pub fn process_deactivate_stake_account( Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair], + &[&config.keypair, stake_authority], recent_blockhash, ) }; @@ -710,12 +787,14 @@ pub fn process_withdraw_stake( stake_account_pubkey: &Pubkey, destination_account_pubkey: &Pubkey, lamports: u64, + withdraw_authority: Option<&Keypair>, ) -> ProcessResult { let (recent_blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?; + let withdraw_authority = withdraw_authority.unwrap_or(&config.keypair); let ixs = vec![stake_instruction::withdraw( stake_account_pubkey, - &config.keypair.pubkey(), + &withdraw_authority.pubkey(), destination_account_pubkey, lamports, )]; @@ -723,7 +802,7 @@ pub fn process_withdraw_stake( let mut tx = Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair], + &[&config.keypair, withdraw_authority], recent_blockhash, ); check_account_for_fee( @@ -879,6 +958,7 @@ pub fn process_delegate_stake( config: &CliConfig, stake_account_pubkey: &Pubkey, vote_account_pubkey: &Pubkey, + stake_authority: Option<&Keypair>, force: bool, sign_only: bool, signers: &Option>, @@ -890,6 +970,7 @@ pub fn process_delegate_stake( (&config.keypair.pubkey(), "cli keypair".to_string()), (stake_account_pubkey, "stake_account_pubkey".to_string()), )?; + let stake_authority = stake_authority.unwrap_or(&config.keypair); // Sanity check the vote account to ensure it is attached to a validator that has recently // voted at the tip of the ledger @@ -936,7 +1017,7 @@ pub fn process_delegate_stake( let ixs = vec![stake_instruction::delegate_stake( stake_account_pubkey, - &config.keypair.pubkey(), + &stake_authority.pubkey(), vote_account_pubkey, )]; let mut tx = if let Some(nonce_account) = &nonce_account { @@ -944,7 +1025,7 @@ pub fn process_delegate_stake( Transaction::new_signed_with_nonce( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair, nonce_authority], + &[&config.keypair, nonce_authority, stake_authority], nonce_account, &nonce_authority.pubkey(), recent_blockhash, @@ -953,7 +1034,7 @@ pub fn process_delegate_stake( Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair], + &[&config.keypair, stake_authority], recent_blockhash, ) }; @@ -983,7 +1064,7 @@ pub fn process_delegate_stake( mod tests { use super::*; use crate::cli::{app, parse_command}; - use solana_sdk::signature::write_keypair; + use solana_sdk::signature::{read_keypair_file, write_keypair}; use tempfile::NamedTempFile; fn make_tmp_file() -> (String, NamedTempFile) { @@ -991,6 +1072,61 @@ mod tests { (String::from(tmp_file.path().to_str().unwrap()), tmp_file) } + fn parse_authorize_tests( + test_commands: &App, + stake_account_pubkey: Pubkey, + authority_keypair_file: &str, + stake_authorize: StakeAuthorize, + ) { + let stake_account_string = stake_account_pubkey.to_string(); + + let (subcommand, authority_flag) = match stake_authorize { + StakeAuthorize::Staker => ("stake-authorize-staker", "--stake-authority"), + StakeAuthorize::Withdrawer => ("stake-authorize-withdrawer", "--withdraw-authority"), + }; + + // Test Staker Subcommand + let test_authorize = test_commands.clone().get_matches_from(vec![ + "test", + &subcommand, + &stake_account_string, + &stake_account_string, + ]); + assert_eq!( + parse_command(&test_authorize).unwrap(), + CliCommandInfo { + command: CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: stake_account_pubkey, + stake_authorize, + authority: None, + }, + require_keypair: true + } + ); + // Test Staker Subcommand w/ authority + let test_authorize = test_commands.clone().get_matches_from(vec![ + "test", + &subcommand, + &stake_account_string, + &stake_account_string, + &authority_flag, + &authority_keypair_file, + ]); + assert_eq!( + parse_command(&test_authorize).unwrap(), + CliCommandInfo { + command: CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: stake_account_pubkey, + stake_authorize, + authority: Some(read_keypair_file(&authority_keypair_file).unwrap().into()), + }, + require_keypair: true + } + ); + } + #[test] fn test_parse_command() { let test_commands = app("test", "desc", "version"); @@ -998,41 +1134,21 @@ mod tests { let stake_account_keypair = Keypair::new(); write_keypair(&stake_account_keypair, tmp_file.as_file_mut()).unwrap(); let stake_account_pubkey = stake_account_keypair.pubkey(); - let stake_account_string = stake_account_pubkey.to_string(); + let (stake_authority_keypair_file, mut tmp_file) = make_tmp_file(); + let stake_authority_keypair = Keypair::new(); + write_keypair(&stake_authority_keypair, tmp_file.as_file_mut()).unwrap(); - let test_authorize_staker = test_commands.clone().get_matches_from(vec![ - "test", - "stake-authorize-staker", - &stake_account_string, - &stake_account_string, - ]); - assert_eq!( - parse_command(&test_authorize_staker).unwrap(), - CliCommandInfo { - command: CliCommand::StakeAuthorize( - stake_account_pubkey, - stake_account_pubkey, - StakeAuthorize::Staker - ), - require_keypair: true - } + parse_authorize_tests( + &test_commands, + stake_account_pubkey, + &stake_authority_keypair_file, + StakeAuthorize::Staker, ); - let test_authorize_withdrawer = test_commands.clone().get_matches_from(vec![ - "test", - "stake-authorize-withdrawer", - &stake_account_string, - &stake_account_string, - ]); - assert_eq!( - parse_command(&test_authorize_withdrawer).unwrap(), - CliCommandInfo { - command: CliCommand::StakeAuthorize( - stake_account_pubkey, - stake_account_pubkey, - StakeAuthorize::Withdrawer - ), - require_keypair: true - } + parse_authorize_tests( + &test_commands, + stake_account_pubkey, + &stake_authority_keypair_file, + StakeAuthorize::Withdrawer, ); // Test CreateStakeAccount SubCommand @@ -1045,9 +1161,9 @@ mod tests { "create-stake-account", &keypair_file, "50", - "--authorized-staker", + "--stake-authority", &authorized_string, - "--authorized-withdrawer", + "--withdraw-authority", &authorized_string, "--custodian", &custodian_string, @@ -1118,6 +1234,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: false, signers: None, @@ -1129,6 +1246,40 @@ mod tests { } ); + // Test DelegateStake Subcommand w/ authority + let vote_account_pubkey = Pubkey::new_rand(); + let vote_account_string = vote_account_pubkey.to_string(); + let test_delegate_stake = test_commands.clone().get_matches_from(vec![ + "test", + "delegate-stake", + &stake_account_string, + &vote_account_string, + "--stake-authority", + &stake_authority_keypair_file, + ]); + assert_eq!( + parse_command(&test_delegate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DelegateStake { + stake_account_pubkey, + vote_account_pubkey, + stake_authority: Some( + read_keypair_file(&stake_authority_keypair_file) + .unwrap() + .into() + ), + force: false, + sign_only: false, + signers: None, + blockhash: None, + nonce_account: None, + nonce_authority: None, + }, + require_keypair: true + } + ); + + // Test DelegateStake Subcommand w/ force let test_delegate_stake = test_commands.clone().get_matches_from(vec![ "test", "delegate-stake", @@ -1142,6 +1293,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: true, sign_only: false, signers: None, @@ -1170,6 +1322,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: false, signers: None, @@ -1194,6 +1347,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: true, signers: None, @@ -1223,6 +1377,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: false, signers: Some(vec![(key1, sig1)]), @@ -1254,6 +1409,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: false, signers: Some(vec![(key1, sig1), (key2, sig2)]), @@ -1278,7 +1434,41 @@ mod tests { assert_eq!( parse_command(&test_withdraw_stake).unwrap(), CliCommandInfo { - command: CliCommand::WithdrawStake(stake_account_pubkey, stake_account_pubkey, 42), + command: CliCommand::WithdrawStake { + stake_account_pubkey, + destination_account_pubkey: stake_account_pubkey, + lamports: 42, + withdraw_authority: None, + }, + require_keypair: true + } + ); + + // Test WithdrawStake Subcommand w/ authority + let test_withdraw_stake = test_commands.clone().get_matches_from(vec![ + "test", + "withdraw-stake", + &stake_account_string, + &stake_account_string, + "42", + "lamports", + "--withdraw-authority", + &stake_authority_keypair_file, + ]); + + assert_eq!( + parse_command(&test_withdraw_stake).unwrap(), + CliCommandInfo { + command: CliCommand::WithdrawStake { + stake_account_pubkey, + destination_account_pubkey: stake_account_pubkey, + lamports: 42, + withdraw_authority: Some( + read_keypair_file(&stake_authority_keypair_file) + .unwrap() + .into() + ), + }, require_keypair: true } ); @@ -1294,6 +1484,35 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, + sign_only: false, + signers: None, + blockhash: None, + nonce_account: None, + nonce_authority: None, + }, + require_keypair: true + } + ); + + // Test DeactivateStake Subcommand w/ authority + let test_deactivate_stake = test_commands.clone().get_matches_from(vec![ + "test", + "deactivate-stake", + &stake_account_string, + "--stake-authority", + &stake_authority_keypair_file, + ]); + assert_eq!( + parse_command(&test_deactivate_stake).unwrap(), + CliCommandInfo { + command: CliCommand::DeactivateStake { + stake_account_pubkey, + stake_authority: Some( + read_keypair_file(&stake_authority_keypair_file) + .unwrap() + .into() + ), sign_only: false, signers: None, blockhash: None, @@ -1319,6 +1538,7 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, sign_only: false, signers: None, blockhash: Some(blockhash), @@ -1340,6 +1560,7 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, sign_only: true, signers: None, blockhash: None, @@ -1366,6 +1587,7 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, sign_only: false, signers: Some(vec![(key1, sig1)]), blockhash: None, @@ -1394,6 +1616,7 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, sign_only: false, signers: Some(vec![(key1, sig1), (key2, sig2)]), blockhash: None, diff --git a/cli/tests/stake.rs b/cli/tests/stake.rs index 2d6985180d..78720ff15e 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -10,7 +10,7 @@ use solana_sdk::{ signature::{read_keypair_file, write_keypair, Keypair, KeypairUtil, Signature}, system_instruction::create_address_with_seed, }; -use solana_stake_program::stake_state::Lockup; +use solana_stake_program::stake_state::{Lockup, StakeAuthorize, StakeState}; use std::fs::remove_dir_all; use std::str::FromStr; use std::sync::mpsc::channel; @@ -111,6 +111,7 @@ fn test_seed_stake_delegation_and_deactivation() { config_validator.command = CliCommand::DelegateStake { stake_account_pubkey: stake_address, vote_account_pubkey: config_vote.keypair.pubkey(), + stake_authority: None, force: true, sign_only: false, signers: None, @@ -123,6 +124,7 @@ fn test_seed_stake_delegation_and_deactivation() { // Deactivate stake config_validator.command = CliCommand::DeactivateStake { stake_account_pubkey: stake_address, + stake_authority: None, sign_only: false, signers: None, blockhash: None, @@ -197,6 +199,7 @@ fn test_stake_delegation_and_deactivation() { config_validator.command = CliCommand::DelegateStake { stake_account_pubkey: config_stake.keypair.pubkey(), vote_account_pubkey: config_vote.keypair.pubkey(), + stake_authority: None, force: true, sign_only: false, signers: None, @@ -209,6 +212,7 @@ fn test_stake_delegation_and_deactivation() { // Deactivate stake config_validator.command = CliCommand::DeactivateStake { stake_account_pubkey: config_stake.keypair.pubkey(), + stake_authority: None, sign_only: false, signers: None, blockhash: None, @@ -287,6 +291,7 @@ fn test_offline_stake_delegation_and_deactivation() { config_validator.command = CliCommand::DelegateStake { stake_account_pubkey: config_stake.keypair.pubkey(), vote_account_pubkey: config_vote.keypair.pubkey(), + stake_authority: None, force: true, sign_only: true, signers: None, @@ -312,6 +317,7 @@ fn test_offline_stake_delegation_and_deactivation() { config_payer.command = CliCommand::DelegateStake { stake_account_pubkey: config_stake.keypair.pubkey(), vote_account_pubkey: config_vote.keypair.pubkey(), + stake_authority: None, force: true, sign_only: false, signers: Some(signers), @@ -324,6 +330,7 @@ fn test_offline_stake_delegation_and_deactivation() { // Deactivate stake offline config_validator.command = CliCommand::DeactivateStake { stake_account_pubkey: config_stake.keypair.pubkey(), + stake_authority: None, sign_only: true, signers: None, blockhash: None, @@ -347,6 +354,7 @@ fn test_offline_stake_delegation_and_deactivation() { // Deactivate stake online config_payer.command = CliCommand::DeactivateStake { stake_account_pubkey: config_stake.keypair.pubkey(), + stake_authority: None, sign_only: false, signers: Some(signers), blockhash: Some(blockhash_str.parse::().unwrap()), @@ -432,6 +440,7 @@ fn test_nonced_stake_delegation_and_deactivation() { config.command = CliCommand::DelegateStake { stake_account_pubkey: stake_keypair.pubkey(), vote_account_pubkey: vote_keypair.pubkey(), + stake_authority: None, force: true, sign_only: false, signers: None, @@ -453,6 +462,7 @@ fn test_nonced_stake_delegation_and_deactivation() { let config_keypair = Keypair::from_bytes(&config.keypair.to_bytes()).unwrap(); config.command = CliCommand::DeactivateStake { stake_account_pubkey: stake_keypair.pubkey(), + stake_authority: None, sign_only: false, signers: None, blockhash: Some(nonce_hash), @@ -464,3 +474,79 @@ fn test_nonced_stake_delegation_and_deactivation() { server.close().unwrap(); remove_dir_all(ledger_path).unwrap(); } + +#[test] +fn test_stake_authorize() { + solana_logger::setup(); + + let (server, leader_data, alice, ledger_path) = new_validator_for_tests(); + let (sender, receiver) = channel(); + run_local_faucet(alice, sender, None); + let faucet_addr = receiver.recv().unwrap(); + + let rpc_client = RpcClient::new_socket(leader_data.rpc); + + let mut config = CliConfig::default(); + config.json_rpc_url = format!("http://{}:{}", leader_data.rpc.ip(), leader_data.rpc.port()); + + request_and_confirm_airdrop(&rpc_client, &faucet_addr, &config.keypair.pubkey(), 100_000) + .unwrap(); + + // Create stake account, identity is authority + let stake_keypair = Keypair::new(); + let stake_account_pubkey = stake_keypair.pubkey(); + let (stake_keypair_file, mut tmp_file) = make_tmp_file(); + write_keypair(&stake_keypair, tmp_file.as_file_mut()).unwrap(); + config.command = CliCommand::CreateStakeAccount { + stake_account: read_keypair_file(&stake_keypair_file).unwrap().into(), + seed: None, + staker: None, + withdrawer: None, + lockup: Lockup::default(), + lamports: 50_000, + }; + process_command(&config).unwrap(); + + // Assign new online stake authority + let online_authority = Keypair::new(); + let online_authority_pubkey = online_authority.pubkey(); + let (online_authority_file, mut tmp_file) = make_tmp_file(); + write_keypair(&online_authority, tmp_file.as_file_mut()).unwrap(); + config.command = CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: online_authority_pubkey, + stake_authorize: StakeAuthorize::Staker, + authority: None, + }; + process_command(&config).unwrap(); + let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); + let stake_state: StakeState = stake_account.state().unwrap(); + let current_authority = match stake_state { + StakeState::Initialized(meta) => meta.authorized.staker, + _ => panic!("Unexpected stake state!"), + }; + assert_eq!(current_authority, online_authority_pubkey); + + // Assign new offline stake authority + let offline_authority = Keypair::new(); + let offline_authority_pubkey = offline_authority.pubkey(); + let (_offline_authority_file, mut tmp_file) = make_tmp_file(); + write_keypair(&offline_authority, tmp_file.as_file_mut()).unwrap(); + config.command = CliCommand::StakeAuthorize { + stake_account_pubkey, + new_authorized_pubkey: offline_authority_pubkey, + stake_authorize: StakeAuthorize::Staker, + authority: Some(read_keypair_file(&online_authority_file).unwrap().into()), + }; + process_command(&config).unwrap(); + let stake_account = rpc_client.get_account(&stake_account_pubkey).unwrap(); + let stake_state: StakeState = stake_account.state().unwrap(); + let current_authority = match stake_state { + StakeState::Initialized(meta) => meta.authorized.staker, + _ => panic!("Unexpected stake state!"), + }; + assert_eq!(current_authority, offline_authority_pubkey); + + server.close().unwrap(); + remove_dir_all(ledger_path).unwrap(); +}