From f90bc20a8b9ae0cab8cd3ad57b7964bca928dbd8 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2020 15:29:47 -0800 Subject: [PATCH] CLI: Plumb stake authorities throughout (#7822) (#7830) automerge --- cli/src/cli.rs | 56 +++++-- cli/src/stake.rs | 353 ++++++++++++++++++++++++++++++++++++--------- cli/tests/stake.rs | 85 ++++++++++- 3 files changed, 414 insertions(+), 80 deletions(-) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index 0d3f7fda93..98d9ef47db 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -167,6 +167,7 @@ pub enum CliCommand { }, DeactivateStake { stake_account_pubkey: Pubkey, + stake_authority: Option, sign_only: bool, signers: Option>, blockhash: Option, @@ -176,6 +177,7 @@ pub enum CliCommand { DelegateStake { stake_account_pubkey: Pubkey, vote_account_pubkey: Pubkey, + stake_authority: Option, force: bool, sign_only: bool, signers: Option>, @@ -191,8 +193,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, @@ -1272,6 +1284,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { // Deactivate stake account CliCommand::DeactivateStake { stake_account_pubkey, + ref stake_authority, sign_only, ref signers, blockhash, @@ -1281,6 +1294,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { &rpc_client, config, &stake_account_pubkey, + stake_authority.as_deref(), *sign_only, signers, *blockhash, @@ -1290,6 +1304,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + ref stake_authority, force, sign_only, ref signers, @@ -1301,6 +1316,7 @@ pub fn process_command(config: &CliConfig) -> ProcessResult { config, &stake_account_pubkey, &vote_account_pubkey, + stake_authority.as_deref(), *force, *sign_only, signers, @@ -1328,27 +1344,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 @@ -2582,13 +2604,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 520aa699e3..fe09f113ed 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; } @@ -91,20 +121,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( @@ -135,6 +165,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") @@ -197,6 +228,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") @@ -219,6 +251,7 @@ impl StakeSubCommands for App<'_, '_> { .validator(is_pubkey_or_keypair) .help("New authorized withdrawer") ) + .arg(withdraw_authority_arg()) ) .subcommand( SubCommand::with_name("deactivate-stake") @@ -231,6 +264,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") @@ -310,6 +344,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") @@ -370,8 +405,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"); @@ -411,6 +453,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, }) } @@ -451,6 +507,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"); @@ -467,6 +530,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( @@ -619,6 +694,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, @@ -627,16 +703,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, @@ -645,7 +722,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, ) }; @@ -677,12 +754,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, )]; @@ -690,7 +769,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( @@ -846,6 +925,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>, @@ -857,6 +937,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 @@ -903,7 +984,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 { @@ -911,7 +992,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, @@ -920,7 +1001,7 @@ pub fn process_delegate_stake( Transaction::new_signed_with_payer( ixs, Some(&config.keypair.pubkey()), - &[&config.keypair], + &[&config.keypair, stake_authority], recent_blockhash, ) }; @@ -950,7 +1031,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) { @@ -958,6 +1039,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"); @@ -965,41 +1101,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 @@ -1012,9 +1128,9 @@ mod tests { "create-stake-account", &keypair_file, "50", - "--authorized-staker", + "--stake-authority", &authorized_string, - "--authorized-withdrawer", + "--withdraw-authority", &authorized_string, "--custodian", &custodian_string, @@ -1083,6 +1199,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: false, signers: None, @@ -1094,6 +1211,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", @@ -1107,6 +1258,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: true, sign_only: false, signers: None, @@ -1135,6 +1287,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: false, signers: None, @@ -1159,6 +1312,7 @@ mod tests { command: CliCommand::DelegateStake { stake_account_pubkey, vote_account_pubkey, + stake_authority: None, force: false, sign_only: true, signers: None, @@ -1188,6 +1342,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)]), @@ -1219,6 +1374,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)]), @@ -1243,7 +1399,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 } ); @@ -1259,6 +1449,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, @@ -1284,6 +1503,7 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, sign_only: false, signers: None, blockhash: Some(blockhash), @@ -1305,6 +1525,7 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, sign_only: true, signers: None, blockhash: None, @@ -1331,6 +1552,7 @@ mod tests { CliCommandInfo { command: CliCommand::DeactivateStake { stake_account_pubkey, + stake_authority: None, sign_only: false, signers: Some(vec![(key1, sig1)]), blockhash: None, @@ -1359,6 +1581,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 0e8b5b8efb..5be0f2de39 100644 --- a/cli/tests/stake.rs +++ b/cli/tests/stake.rs @@ -9,7 +9,7 @@ use solana_sdk::{ pubkey::Pubkey, signature::{read_keypair_file, write_keypair, Keypair, KeypairUtil, Signature}, }; -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; @@ -99,6 +99,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, @@ -111,6 +112,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, @@ -187,6 +189,7 @@ fn test_stake_delegation_and_deactivation_offline() { 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, @@ -212,6 +215,7 @@ fn test_stake_delegation_and_deactivation_offline() { 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), @@ -224,6 +228,7 @@ fn test_stake_delegation_and_deactivation_offline() { // 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, @@ -247,6 +252,7 @@ fn test_stake_delegation_and_deactivation_offline() { // 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()), @@ -329,6 +335,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, @@ -350,6 +357,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), @@ -361,3 +369,78 @@ 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(), + 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(); +}