Cli: check current authorities before attempting to change them (#19853)
* Stake-authorize: check account current authority * Stake-set-lockup: check account current custodian * Make helper fn pub(crate) * Vote-authorize: check account current authority
This commit is contained in:
		@@ -1973,17 +1973,36 @@ mod tests {
 | 
			
		||||
        let result = process_command(&config);
 | 
			
		||||
        assert!(result.is_ok());
 | 
			
		||||
 | 
			
		||||
        let vote_account_info_response = json!(Response {
 | 
			
		||||
            context: RpcResponseContext { slot: 1 },
 | 
			
		||||
            value: json!({
 | 
			
		||||
                "data": ["KLUv/QBYNQIAtAIBAAAAbnoc3Smwt4/ROvTFWY/v9O8qlxZuPKby5Pv8zYBQW/EFAAEAAB8ACQD6gx92zAiAAecDP4B2XeEBSIx7MQeung==", "base64+zstd"],
 | 
			
		||||
                "lamports": 42,
 | 
			
		||||
                "owner": "Vote111111111111111111111111111111111111111",
 | 
			
		||||
                "executable": false,
 | 
			
		||||
                "rentEpoch": 1,
 | 
			
		||||
            }),
 | 
			
		||||
        });
 | 
			
		||||
        let mut mocks = HashMap::new();
 | 
			
		||||
        mocks.insert(RpcRequest::GetAccountInfo, vote_account_info_response);
 | 
			
		||||
        let rpc_client = RpcClient::new_mock_with_mocks("".to_string(), mocks);
 | 
			
		||||
        let mut vote_config = CliConfig {
 | 
			
		||||
            rpc_client: Some(Arc::new(rpc_client)),
 | 
			
		||||
            json_rpc_url: "http://127.0.0.1:8899".to_string(),
 | 
			
		||||
            ..CliConfig::default()
 | 
			
		||||
        };
 | 
			
		||||
        let current_authority = keypair_from_seed(&[5; 32]).unwrap();
 | 
			
		||||
        let new_authorized_pubkey = solana_sdk::pubkey::new_rand();
 | 
			
		||||
        config.signers = vec![&bob_keypair];
 | 
			
		||||
        config.command = CliCommand::VoteAuthorize {
 | 
			
		||||
        vote_config.signers = vec![¤t_authority];
 | 
			
		||||
        vote_config.command = CliCommand::VoteAuthorize {
 | 
			
		||||
            vote_account_pubkey: bob_pubkey,
 | 
			
		||||
            new_authorized_pubkey,
 | 
			
		||||
            vote_authorize: VoteAuthorize::Voter,
 | 
			
		||||
            vote_authorize: VoteAuthorize::Withdrawer,
 | 
			
		||||
            memo: None,
 | 
			
		||||
            authorized: 0,
 | 
			
		||||
            new_authorized: None,
 | 
			
		||||
        };
 | 
			
		||||
        let result = process_command(&config);
 | 
			
		||||
        let result = process_command(&vote_config);
 | 
			
		||||
        assert!(result.is_ok());
 | 
			
		||||
 | 
			
		||||
        let new_identity_keypair = Keypair::new();
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@ use solana_sdk::{
 | 
			
		||||
    account::from_account,
 | 
			
		||||
    account_utils::StateMut,
 | 
			
		||||
    clock::{Clock, UnixTimestamp, SECONDS_PER_DAY},
 | 
			
		||||
    commitment_config::CommitmentConfig,
 | 
			
		||||
    epoch_schedule::EpochSchedule,
 | 
			
		||||
    message::Message,
 | 
			
		||||
    pubkey::Pubkey,
 | 
			
		||||
@@ -1353,6 +1354,15 @@ pub fn process_stake_authorize(
 | 
			
		||||
) -> ProcessResult {
 | 
			
		||||
    let mut ixs = Vec::new();
 | 
			
		||||
    let custodian = custodian.map(|index| config.signers[index]);
 | 
			
		||||
    let current_stake_account = if !sign_only {
 | 
			
		||||
        Some(get_stake_account_state(
 | 
			
		||||
            rpc_client,
 | 
			
		||||
            stake_account_pubkey,
 | 
			
		||||
            config.commitment,
 | 
			
		||||
        )?)
 | 
			
		||||
    } else {
 | 
			
		||||
        None
 | 
			
		||||
    };
 | 
			
		||||
    for StakeAuthorizationIndexed {
 | 
			
		||||
        authorization_type,
 | 
			
		||||
        new_authority_pubkey,
 | 
			
		||||
@@ -1365,6 +1375,29 @@ pub fn process_stake_authorize(
 | 
			
		||||
            (new_authority_pubkey, "new_authorized_pubkey".to_string()),
 | 
			
		||||
        )?;
 | 
			
		||||
        let authority = config.signers[*authority];
 | 
			
		||||
        if let Some(current_stake_account) = current_stake_account {
 | 
			
		||||
            let authorized = match current_stake_account {
 | 
			
		||||
                StakeState::Stake(Meta { authorized, .. }, ..) => Some(authorized),
 | 
			
		||||
                StakeState::Initialized(Meta { authorized, .. }) => Some(authorized),
 | 
			
		||||
                _ => None,
 | 
			
		||||
            };
 | 
			
		||||
            if let Some(authorized) = authorized {
 | 
			
		||||
                match authorization_type {
 | 
			
		||||
                    StakeAuthorize::Staker => {
 | 
			
		||||
                        check_current_authority(&authorized.staker, &authority.pubkey())?;
 | 
			
		||||
                    }
 | 
			
		||||
                    StakeAuthorize::Withdrawer => {
 | 
			
		||||
                        check_current_authority(&authorized.withdrawer, &authority.pubkey())?;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                return Err(CliError::RpcRequestError(format!(
 | 
			
		||||
                    "{:?} is not an Initialized or Delegated stake account",
 | 
			
		||||
                    stake_account_pubkey,
 | 
			
		||||
                ))
 | 
			
		||||
                .into());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if new_authority_signer.is_some() {
 | 
			
		||||
            ixs.push(stake_instruction::authorize_checked(
 | 
			
		||||
                stake_account_pubkey, // stake account to update
 | 
			
		||||
@@ -1892,6 +1925,26 @@ pub fn process_stake_set_lockup(
 | 
			
		||||
    let nonce_authority = config.signers[nonce_authority];
 | 
			
		||||
    let fee_payer = config.signers[fee_payer];
 | 
			
		||||
 | 
			
		||||
    if !sign_only {
 | 
			
		||||
        let state = get_stake_account_state(rpc_client, stake_account_pubkey, config.commitment)?;
 | 
			
		||||
        let lockup = match state {
 | 
			
		||||
            StakeState::Stake(Meta { lockup, .. }, ..) => Some(lockup),
 | 
			
		||||
            StakeState::Initialized(Meta { lockup, .. }) => Some(lockup),
 | 
			
		||||
            _ => None,
 | 
			
		||||
        };
 | 
			
		||||
        if let Some(lockup) = lockup {
 | 
			
		||||
            if lockup.custodian != Pubkey::default() {
 | 
			
		||||
                check_current_authority(&lockup.custodian, &custodian.pubkey())?;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            return Err(CliError::RpcRequestError(format!(
 | 
			
		||||
                "{:?} is not an Initialized or Delegated stake account",
 | 
			
		||||
                stake_account_pubkey,
 | 
			
		||||
            ))
 | 
			
		||||
            .into());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let message = if let Some(nonce_account) = &nonce_account {
 | 
			
		||||
        Message::new_with_nonce(
 | 
			
		||||
            ixs,
 | 
			
		||||
@@ -2034,6 +2087,47 @@ pub fn build_stake_state(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn get_stake_account_state(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    stake_account_pubkey: &Pubkey,
 | 
			
		||||
    commitment_config: CommitmentConfig,
 | 
			
		||||
) -> Result<StakeState, Box<dyn std::error::Error>> {
 | 
			
		||||
    let stake_account = rpc_client
 | 
			
		||||
        .get_account_with_commitment(stake_account_pubkey, commitment_config)?
 | 
			
		||||
        .value
 | 
			
		||||
        .ok_or_else(|| {
 | 
			
		||||
            CliError::RpcRequestError(format!("{:?} account does not exist", stake_account_pubkey))
 | 
			
		||||
        })?;
 | 
			
		||||
    if stake_account.owner != stake::program::id() {
 | 
			
		||||
        return Err(CliError::RpcRequestError(format!(
 | 
			
		||||
            "{:?} is not a stake account",
 | 
			
		||||
            stake_account_pubkey,
 | 
			
		||||
        ))
 | 
			
		||||
        .into());
 | 
			
		||||
    }
 | 
			
		||||
    stake_account.state().map_err(|err| {
 | 
			
		||||
        CliError::RpcRequestError(format!(
 | 
			
		||||
            "Account data could not be deserialized to stake state: {}",
 | 
			
		||||
            err
 | 
			
		||||
        ))
 | 
			
		||||
        .into()
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) fn check_current_authority(
 | 
			
		||||
    account_current_authority: &Pubkey,
 | 
			
		||||
    provided_current_authority: &Pubkey,
 | 
			
		||||
) -> Result<(), CliError> {
 | 
			
		||||
    if account_current_authority != provided_current_authority {
 | 
			
		||||
        Err(CliError::RpcRequestError(format!(
 | 
			
		||||
            "Invalid current authority provided: {:?}, expected {:?}",
 | 
			
		||||
            provided_current_authority, account_current_authority
 | 
			
		||||
        )))
 | 
			
		||||
    } else {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn get_epoch_boundary_timestamps(
 | 
			
		||||
    rpc_client: &RpcClient,
 | 
			
		||||
    reward: &RpcInflationReward,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ use crate::{
 | 
			
		||||
    },
 | 
			
		||||
    memo::WithMemo,
 | 
			
		||||
    spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
 | 
			
		||||
    stake::check_current_authority,
 | 
			
		||||
};
 | 
			
		||||
use clap::{value_t_or_exit, App, Arg, ArgMatches, SubCommand};
 | 
			
		||||
use solana_clap_utils::{
 | 
			
		||||
@@ -730,6 +731,25 @@ pub fn process_vote_authorize(
 | 
			
		||||
        (&authorized.pubkey(), "authorized_account".to_string()),
 | 
			
		||||
        (new_authorized_pubkey, "new_authorized_pubkey".to_string()),
 | 
			
		||||
    )?;
 | 
			
		||||
    let (_, vote_state) = get_vote_account(rpc_client, vote_account_pubkey, config.commitment)?;
 | 
			
		||||
    match vote_authorize {
 | 
			
		||||
        VoteAuthorize::Voter => {
 | 
			
		||||
            let current_authorized_voter = vote_state
 | 
			
		||||
                .authorized_voters()
 | 
			
		||||
                .last()
 | 
			
		||||
                .ok_or_else(|| {
 | 
			
		||||
                    CliError::RpcRequestError(
 | 
			
		||||
                        "Invalid vote account state; no authorized voters found".to_string(),
 | 
			
		||||
                    )
 | 
			
		||||
                })?
 | 
			
		||||
                .1;
 | 
			
		||||
            check_current_authority(current_authorized_voter, &authorized.pubkey())?
 | 
			
		||||
        }
 | 
			
		||||
        VoteAuthorize::Withdrawer => {
 | 
			
		||||
            check_current_authority(&vote_state.authorized_withdrawer, &authorized.pubkey())?
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let latest_blockhash = rpc_client.get_latest_blockhash()?;
 | 
			
		||||
    let vote_ix = if new_authorized_signer.is_some() {
 | 
			
		||||
        vote_instruction::authorize_checked(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user