stake-pool: Add ability to remove a validator that has deactivating transient stake (#1624)

* Add status enum

* Add ability to remove validator with transient stake

* Only account validator stake if active

* Fix merge conflicts
This commit is contained in:
Jon Cinque 2021-04-27 13:24:39 +02:00 committed by GitHub
parent 7d666b86ce
commit 14bdbdc3ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 407 additions and 69 deletions

View File

@ -519,7 +519,7 @@ pub fn update_stake_pool_balance(
let accounts = vec![ let accounts = vec![
AccountMeta::new(*stake_pool, false), AccountMeta::new(*stake_pool, false),
AccountMeta::new_readonly(*withdraw_authority, false), AccountMeta::new_readonly(*withdraw_authority, false),
AccountMeta::new_readonly(*validator_list_storage, false), AccountMeta::new(*validator_list_storage, false),
AccountMeta::new_readonly(*reserve_stake, false), AccountMeta::new_readonly(*reserve_stake, false),
AccountMeta::new(*manager_fee_account, false), AccountMeta::new(*manager_fee_account, false),
AccountMeta::new(*stake_pool_mint, false), AccountMeta::new(*stake_pool_mint, false),

View File

@ -7,7 +7,7 @@ use {
find_deposit_authority_program_address, find_deposit_authority_program_address,
instruction::StakePoolInstruction, instruction::StakePoolInstruction,
minimum_reserve_lamports, minimum_stake_lamports, stake_program, minimum_reserve_lamports, minimum_stake_lamports, stake_program,
state::{AccountType, Fee, StakePool, ValidatorList, ValidatorStakeInfo}, state::{AccountType, Fee, StakePool, StakeStatus, ValidatorList, ValidatorStakeInfo},
AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MINIMUM_ACTIVE_STAKE, TRANSIENT_STAKE_SEED, AUTHORITY_DEPOSIT, AUTHORITY_WITHDRAW, MINIMUM_ACTIVE_STAKE, TRANSIENT_STAKE_SEED,
}, },
borsh::{BorshDeserialize, BorshSerialize}, borsh::{BorshDeserialize, BorshSerialize},
@ -15,7 +15,7 @@ use {
solana_program::{ solana_program::{
account_info::next_account_info, account_info::next_account_info,
account_info::AccountInfo, account_info::AccountInfo,
clock::Clock, clock::{Clock, Epoch},
decode_error::DecodeError, decode_error::DecodeError,
entrypoint::ProgramResult, entrypoint::ProgramResult,
msg, msg,
@ -747,6 +747,7 @@ impl Processor {
)?; )?;
validator_list.validators.push(ValidatorStakeInfo { validator_list.validators.push(ValidatorStakeInfo {
status: StakeStatus::Active,
vote_account_address, vote_account_address,
stake_lamports: stake_lamports.saturating_sub(minimum_lamport_amount), stake_lamports: stake_lamports.saturating_sub(minimum_lamport_amount),
last_update_epoch: clock.epoch, last_update_epoch: clock.epoch,
@ -814,20 +815,16 @@ impl Processor {
transient_stake_account_info.key, transient_stake_account_info.key,
&vote_account_address, &vote_account_address,
)?; )?;
// check that the transient stake account doesn't exist
if get_stake_state(transient_stake_account_info).is_ok() { let maybe_validator_list_entry = validator_list.find_mut(&vote_account_address);
if maybe_validator_list_entry.is_none() {
msg!( msg!(
"Transient stake {} exists, can't remove stake {} on validator {}", "Vote account {} not found in stake pool",
transient_stake_account_info.key,
stake_account_info.key,
vote_account_address vote_account_address
); );
return Err(StakePoolError::WrongStakeState.into());
}
if !validator_list.contains(&vote_account_address) {
return Err(StakePoolError::ValidatorNotFound.into()); return Err(StakePoolError::ValidatorNotFound.into());
} }
let mut validator_list_entry = maybe_validator_list_entry.unwrap();
let stake_lamports = **stake_account_info.lamports.borrow(); let stake_lamports = **stake_account_info.lamports.borrow();
let required_lamports = minimum_stake_lamports(&meta); let required_lamports = minimum_stake_lamports(&meta);
@ -840,6 +837,24 @@ impl Processor {
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into()); return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
} }
// check that the transient stake account doesn't exist
let new_status = if let Ok((_meta, stake)) = get_stake_state(transient_stake_account_info) {
if stake.delegation.deactivation_epoch == Epoch::MAX {
msg!(
"Transient stake {} activating, can't remove stake {} on validator {}",
transient_stake_account_info.key,
stake_account_info.key,
vote_account_address
);
return Err(StakePoolError::WrongStakeState.into());
} else {
// stake is deactivating, mark the entry as such
StakeStatus::DeactivatingTransient
}
} else {
StakeStatus::ReadyForRemoval
};
Self::stake_authorize_signed( Self::stake_authorize_signed(
stake_pool_info.key, stake_pool_info.key,
stake_account_info.clone(), stake_account_info.clone(),
@ -851,9 +866,13 @@ impl Processor {
stake_program_info.clone(), stake_program_info.clone(),
)?; )?;
validator_list match new_status {
.validators StakeStatus::DeactivatingTransient => validator_list_entry.status = new_status,
.retain(|item| item.vote_account_address != vote_account_address); StakeStatus::ReadyForRemoval => validator_list
.validators
.retain(|item| item.vote_account_address != vote_account_address),
_ => unreachable!(),
}
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?; validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
Ok(()) Ok(())
@ -1239,6 +1258,11 @@ impl Processor {
stake_history_info.clone(), stake_history_info.clone(),
stake_program_info.clone(), stake_program_info.clone(),
)?; )?;
if validator_stake_record.status == StakeStatus::DeactivatingTransient {
// the validator stake was previously removed, and
// now this entry can be removed totally
validator_stake_record.status = StakeStatus::ReadyForRemoval;
}
} }
} }
Some(stake_program::StakeState::Stake(_, stake)) => { Some(stake_program::StakeState::Stake(_, stake)) => {
@ -1257,6 +1281,11 @@ impl Processor {
stake_history_info.clone(), stake_history_info.clone(),
stake_program_info.clone(), stake_program_info.clone(),
)?; )?;
if validator_stake_record.status == StakeStatus::DeactivatingTransient {
// the validator stake was previously removed, and
// now this entry can be removed totally
validator_stake_record.status = StakeStatus::ReadyForRemoval;
}
} else if stake.delegation.activation_epoch < clock.epoch { } else if stake.delegation.activation_epoch < clock.epoch {
if let Some(stake_program::StakeState::Stake(_, validator_stake)) = if let Some(stake_program::StakeState::Stake(_, validator_stake)) =
validator_stake_state validator_stake_state
@ -1298,15 +1327,19 @@ impl Processor {
// * any other state / not a stake -> error state, but account for transient stake // * any other state / not a stake -> error state, but account for transient stake
match validator_stake_state { match validator_stake_state {
Some(stake_program::StakeState::Stake(meta, _)) => { Some(stake_program::StakeState::Stake(meta, _)) => {
stake_lamports += validator_stake_info if validator_stake_record.status == StakeStatus::Active {
.lamports() stake_lamports += validator_stake_info
.saturating_sub(minimum_stake_lamports(&meta)); .lamports()
.saturating_sub(minimum_stake_lamports(&meta));
} else {
msg!("Validator stake account no longer part of the pool, ignoring");
}
} }
Some(stake_program::StakeState::Initialized(_)) Some(stake_program::StakeState::Initialized(_))
| Some(stake_program::StakeState::Uninitialized) | Some(stake_program::StakeState::Uninitialized)
| Some(stake_program::StakeState::RewardsPool) | Some(stake_program::StakeState::RewardsPool)
| None => { | None => {
msg!("Validator stake account no longer part of the pool, not considering"); msg!("Validator stake account no longer part of the pool, ignoring");
} }
} }
@ -1355,7 +1388,7 @@ impl Processor {
return Err(ProgramError::IncorrectProgramId); return Err(ProgramError::IncorrectProgramId);
} }
let validator_list = let mut validator_list =
try_from_slice_unchecked::<ValidatorList>(&validator_list_info.data.borrow())?; try_from_slice_unchecked::<ValidatorList>(&validator_list_info.data.borrow())?;
if !validator_list.is_valid() { if !validator_list.is_valid() {
return Err(StakePoolError::InvalidState.into()); return Err(StakePoolError::InvalidState.into());
@ -1375,7 +1408,7 @@ impl Processor {
msg!("Reserve stake account in unknown state, aborting"); msg!("Reserve stake account in unknown state, aborting");
return Err(StakePoolError::WrongStakeState.into()); return Err(StakePoolError::WrongStakeState.into());
}; };
for validator_stake_record in validator_list.validators { for validator_stake_record in &validator_list.validators {
if validator_stake_record.last_update_epoch < clock.epoch { if validator_stake_record.last_update_epoch < clock.epoch {
return Err(StakePoolError::StakeListOutOfDate.into()); return Err(StakePoolError::StakeListOutOfDate.into());
} }
@ -1406,6 +1439,10 @@ impl Processor {
.checked_add(fee) .checked_add(fee)
.ok_or(StakePoolError::CalculationFailure)?; .ok_or(StakePoolError::CalculationFailure)?;
} }
validator_list
.validators
.retain(|item| item.status != StakeStatus::ReadyForRemoval);
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
stake_pool.total_stake_lamports = total_stake_lamports; stake_pool.total_stake_lamports = total_stake_lamports;
stake_pool.last_update_epoch = clock.epoch; stake_pool.last_update_epoch = clock.epoch;
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?; stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
@ -1507,6 +1544,11 @@ impl Processor {
.find_mut(&vote_account_address) .find_mut(&vote_account_address)
.ok_or(StakePoolError::ValidatorNotFound)?; .ok_or(StakePoolError::ValidatorNotFound)?;
if validator_list_item.status != StakeStatus::Active {
msg!("Validator is marked for removal and no longer accepting deposits");
return Err(StakePoolError::ValidatorNotFound.into());
}
let stake_lamports = **stake_info.lamports.borrow(); let stake_lamports = **stake_info.lamports.borrow();
let new_pool_tokens = stake_pool let new_pool_tokens = stake_pool
.calc_pool_tokens_for_deposit(stake_lamports) .calc_pool_tokens_for_deposit(stake_lamports)
@ -1684,6 +1726,11 @@ impl Processor {
.find_mut(&vote_account_address) .find_mut(&vote_account_address)
.ok_or(StakePoolError::ValidatorNotFound)?; .ok_or(StakePoolError::ValidatorNotFound)?;
if validator_list_item.status != StakeStatus::Active {
msg!("Validator is marked for removal and no longer allowing withdrawals");
return Err(StakePoolError::ValidatorNotFound.into());
}
let required_lamports = minimum_stake_lamports(&meta); let required_lamports = minimum_stake_lamports(&meta);
let current_lamports = stake_split_from.lamports(); let current_lamports = stake_split_from.lamports();
let remaining_lamports = current_lamports.saturating_sub(withdraw_lamports); let remaining_lamports = current_lamports.saturating_sub(withdraw_lamports);

View File

@ -285,10 +285,32 @@ pub struct ValidatorList {
pub validators: Vec<ValidatorStakeInfo>, pub validators: Vec<ValidatorStakeInfo>,
} }
/// Status of the stake account in the validator list, for accounting
#[derive(Copy, Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum StakeStatus {
/// Stake account is active, there may be a transient stake as well
Active,
/// Only transient stake account exists, when a transient stake is
/// deactivating during validator removal
DeactivatingTransient,
/// No more validator stake accounts exist, entry ready for removal during
/// `UpdateStakePoolBalance`
ReadyForRemoval,
}
impl Default for StakeStatus {
fn default() -> Self {
Self::Active
}
}
/// Information about the singe validator stake account /// Information about the singe validator stake account
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Copy, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct ValidatorStakeInfo { pub struct ValidatorStakeInfo {
/// Status of the validator stake account
pub status: StakeStatus,
/// Validator vote account address /// Validator vote account address
pub vote_account_address: Pubkey, pub vote_account_address: Pubkey,
@ -314,7 +336,7 @@ impl ValidatorList {
/// Calculate the number of validator entries that fit in the provided length /// Calculate the number of validator entries that fit in the provided length
pub fn calculate_max_validators(buffer_length: usize) -> usize { pub fn calculate_max_validators(buffer_length: usize) -> usize {
let header_size = 1 + 4 + 4; let header_size = 1 + 4 + 4;
buffer_length.saturating_sub(header_size) / 48 buffer_length.saturating_sub(header_size) / 49
} }
/// Check if contains validator with particular pubkey /// Check if contains validator with particular pubkey
@ -402,16 +424,19 @@ mod test {
max_validators, max_validators,
validators: vec![ validators: vec![
ValidatorStakeInfo { ValidatorStakeInfo {
status: StakeStatus::Active,
vote_account_address: Pubkey::new_from_array([1; 32]), vote_account_address: Pubkey::new_from_array([1; 32]),
stake_lamports: 123456789, stake_lamports: 123456789,
last_update_epoch: 987654321, last_update_epoch: 987654321,
}, },
ValidatorStakeInfo { ValidatorStakeInfo {
status: StakeStatus::DeactivatingTransient,
vote_account_address: Pubkey::new_from_array([2; 32]), vote_account_address: Pubkey::new_from_array([2; 32]),
stake_lamports: 998877665544, stake_lamports: 998877665544,
last_update_epoch: 11223445566, last_update_epoch: 11223445566,
}, },
ValidatorStakeInfo { ValidatorStakeInfo {
status: StakeStatus::ReadyForRemoval,
vote_account_address: Pubkey::new_from_array([3; 32]), vote_account_address: Pubkey::new_from_array([3; 32]),
stake_lamports: 0, stake_lamports: 0,
last_update_epoch: 999999999999999, last_update_epoch: 999999999999999,

View File

@ -49,7 +49,8 @@ async fn setup() -> (
&validator_stake_account, &validator_stake_account,
100_000_000, 100_000_000,
) )
.await; .await
.unwrap();
let lamports = deposit_info.stake_lamports / 2; let lamports = deposit_info.stake_lamports / 2;

View File

@ -1010,7 +1010,7 @@ pub async fn simple_deposit(
stake_pool_accounts: &StakePoolAccounts, stake_pool_accounts: &StakePoolAccounts,
validator_stake_account: &ValidatorStakeAccount, validator_stake_account: &ValidatorStakeAccount,
stake_lamports: u64, stake_lamports: u64,
) -> DepositStakeAccount { ) -> Option<DepositStakeAccount> {
let authority = Keypair::new(); let authority = Keypair::new();
// make stake account // make stake account
let stake = Keypair::new(); let stake = Keypair::new();
@ -1064,11 +1064,11 @@ pub async fn simple_deposit(
&authority, &authority,
) )
.await .await
.unwrap(); .ok()?;
let pool_tokens = get_token_balance(banks_client, &pool_account.pubkey()).await; let pool_tokens = get_token_balance(banks_client, &pool_account.pubkey()).await;
DepositStakeAccount { Some(DepositStakeAccount {
authority, authority,
stake, stake,
pool_account, pool_account,
@ -1076,7 +1076,7 @@ pub async fn simple_deposit(
pool_tokens, pool_tokens,
vote_account, vote_account,
validator_stake_account, validator_stake_account,
} })
} }
pub async fn get_validator_list_sum( pub async fn get_validator_list_sum(

View File

@ -54,7 +54,8 @@ async fn setup() -> (
&validator_stake_account, &validator_stake_account,
5_000_000, 5_000_000,
) )
.await; .await
.unwrap();
( (
banks_client, banks_client,

View File

@ -51,7 +51,8 @@ async fn setup() -> (
&validator_stake_account, &validator_stake_account,
TEST_STAKE_AMOUNT, TEST_STAKE_AMOUNT,
) )
.await; .await
.unwrap();
stake_accounts.push(validator_stake_account); stake_accounts.push(validator_stake_account);
} }

View File

@ -9,7 +9,10 @@ use {
solana_program_test::*, solana_program_test::*,
solana_sdk::signature::{Keypair, Signer}, solana_sdk::signature::{Keypair, Signer},
spl_stake_pool::{ spl_stake_pool::{
stake_program, state::StakePool, MAX_VALIDATORS_TO_UPDATE, MINIMUM_ACTIVE_STAKE, borsh::try_from_slice_unchecked,
stake_program,
state::{StakePool, StakeStatus, ValidatorList},
MAX_VALIDATORS_TO_UPDATE, MINIMUM_ACTIVE_STAKE,
}, },
}; };
@ -474,7 +477,139 @@ async fn merge_into_validator_stake() {
} }
#[tokio::test] #[tokio::test]
async fn max_validators() {} #[ignore]
async fn merge_transient_stake_after_remove() {
let (mut context, stake_pool_accounts, stake_accounts, lamports, reserve_lamports, mut slot) =
setup(1).await;
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let deactivated_lamports = lamports + stake_rent;
let new_authority = Pubkey::new_unique();
// Decrease and remove all validators
for stake_account in &stake_accounts {
let error = stake_pool_accounts
.decrease_validator_stake(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&stake_account.stake_account,
&stake_account.transient_stake_account,
deactivated_lamports,
)
.await;
assert!(error.is_none());
let error = stake_pool_accounts
.remove_validator_from_pool(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&new_authority,
&stake_account.stake_account,
&stake_account.transient_stake_account,
)
.await;
assert!(error.is_none());
}
// Warp forward to merge time
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
slot += slots_per_epoch;
context.warp_to_slot(slot).unwrap();
// Update without merge, status should be DeactivatingTransient
let error = stake_pool_accounts
.update_all(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
stake_accounts
.iter()
.map(|v| v.vote.pubkey())
.collect::<Vec<Pubkey>>()
.as_slice(),
true,
)
.await;
assert!(error.is_none());
let validator_list = get_account(
&mut context.banks_client,
&stake_pool_accounts.validator_list.pubkey(),
)
.await;
let validator_list =
try_from_slice_unchecked::<ValidatorList>(validator_list.data.as_slice()).unwrap();
assert_eq!(validator_list.validators.len(), 1);
assert_eq!(
validator_list.validators[0].status,
StakeStatus::DeactivatingTransient
);
assert_eq!(
validator_list.validators[0].stake_lamports,
deactivated_lamports
);
// Update with merge, status should be ReadyForRemoval and no lamports
let error = stake_pool_accounts
.update_validator_list_balance(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
stake_accounts
.iter()
.map(|v| v.vote.pubkey())
.collect::<Vec<Pubkey>>()
.as_slice(),
false,
)
.await;
assert!(error.is_none());
let validator_list = get_account(
&mut context.banks_client,
&stake_pool_accounts.validator_list.pubkey(),
)
.await;
let validator_list =
try_from_slice_unchecked::<ValidatorList>(validator_list.data.as_slice()).unwrap();
assert_eq!(validator_list.validators.len(), 1);
assert_eq!(
validator_list.validators[0].status,
StakeStatus::ReadyForRemoval
);
assert_eq!(validator_list.validators[0].stake_lamports, 0);
let reserve_stake = context
.banks_client
.get_account(stake_pool_accounts.reserve_stake.pubkey())
.await
.unwrap()
.unwrap();
assert_eq!(
reserve_stake.lamports,
reserve_lamports + deactivated_lamports + stake_rent + 1
);
// Update stake pool balance, should be gone
let error = stake_pool_accounts
.update_stake_pool_balance(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
)
.await;
assert!(error.is_none());
let validator_list = get_account(
&mut context.banks_client,
&stake_pool_accounts.validator_list.pubkey(),
)
.await;
let validator_list =
try_from_slice_unchecked::<ValidatorList>(validator_list.data.as_slice()).unwrap();
assert_eq!(validator_list.validators.len(), 0);
}
#[tokio::test] #[tokio::test]
async fn fail_with_uninitialized_validator_list() {} // TODO async fn fail_with_uninitialized_validator_list() {} // TODO

View File

@ -86,6 +86,7 @@ async fn success() {
account_type: state::AccountType::ValidatorList, account_type: state::AccountType::ValidatorList,
max_validators: stake_pool_accounts.max_validators, max_validators: stake_pool_accounts.max_validators,
validators: vec![state::ValidatorStakeInfo { validators: vec![state::ValidatorStakeInfo {
status: state::StakeStatus::Active,
vote_account_address: user_stake.vote.pubkey(), vote_account_address: user_stake.vote.pubkey(),
last_update_epoch: 0, last_update_epoch: 0,
stake_lamports: 0, stake_lamports: 0,

View File

@ -38,8 +38,8 @@ async fn setup() -> (
.await .await
.unwrap(); .unwrap();
let user_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey()); let validator_stake = ValidatorStakeAccount::new(&stake_pool_accounts.stake_pool.pubkey());
user_stake validator_stake
.create_and_delegate( .create_and_delegate(
&mut banks_client, &mut banks_client,
&payer, &payer,
@ -53,7 +53,7 @@ async fn setup() -> (
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
&user_stake.stake_account, &validator_stake.stake_account,
) )
.await; .await;
assert!(error.is_none()); assert!(error.is_none());
@ -63,13 +63,13 @@ async fn setup() -> (
payer, payer,
recent_blockhash, recent_blockhash,
stake_pool_accounts, stake_pool_accounts,
user_stake, validator_stake,
) )
} }
#[tokio::test] #[tokio::test]
async fn success() { async fn success() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
let new_authority = Pubkey::new_unique(); let new_authority = Pubkey::new_unique();
@ -79,8 +79,8 @@ async fn success() {
&payer, &payer,
&recent_blockhash, &recent_blockhash,
&new_authority, &new_authority,
&user_stake.stake_account, &validator_stake.stake_account,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
) )
.await; .await;
assert!(error.is_none()); assert!(error.is_none());
@ -103,7 +103,7 @@ async fn success() {
); );
// Check of stake account authority has changed // Check of stake account authority has changed
let stake = get_account(&mut banks_client, &user_stake.stake_account).await; let stake = get_account(&mut banks_client, &validator_stake.stake_account).await;
let stake_state = deserialize::<stake_program::StakeState>(&stake.data).unwrap(); let stake_state = deserialize::<stake_program::StakeState>(&stake.data).unwrap();
match stake_state { match stake_state {
stake_program::StakeState::Stake(meta, _) => { stake_program::StakeState::Stake(meta, _) => {
@ -116,7 +116,7 @@ async fn success() {
#[tokio::test] #[tokio::test]
async fn fail_with_wrong_stake_program_id() { async fn fail_with_wrong_stake_program_id() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
let wrong_stake_program = Pubkey::new_unique(); let wrong_stake_program = Pubkey::new_unique();
@ -128,8 +128,8 @@ async fn fail_with_wrong_stake_program_id() {
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
AccountMeta::new_readonly(new_authority, false), AccountMeta::new_readonly(new_authority, false),
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false), AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
AccountMeta::new(user_stake.stake_account, false), AccountMeta::new(validator_stake.stake_account, false),
AccountMeta::new_readonly(user_stake.transient_stake_account, false), AccountMeta::new_readonly(validator_stake.transient_stake_account, false),
AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(wrong_stake_program, false), AccountMeta::new_readonly(wrong_stake_program, false),
]; ];
@ -162,7 +162,7 @@ async fn fail_with_wrong_stake_program_id() {
#[tokio::test] #[tokio::test]
async fn fail_with_wrong_validator_list_account() { async fn fail_with_wrong_validator_list_account() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
let wrong_validator_list = Keypair::new(); let wrong_validator_list = Keypair::new();
@ -176,8 +176,8 @@ async fn fail_with_wrong_validator_list_account() {
&stake_pool_accounts.withdraw_authority, &stake_pool_accounts.withdraw_authority,
&new_authority, &new_authority,
&wrong_validator_list.pubkey(), &wrong_validator_list.pubkey(),
&user_stake.stake_account, &validator_stake.stake_account,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
) )
.unwrap()], .unwrap()],
Some(&payer.pubkey()), Some(&payer.pubkey()),
@ -203,14 +203,14 @@ async fn fail_with_wrong_validator_list_account() {
#[tokio::test] #[tokio::test]
async fn fail_not_at_minimum() { async fn fail_not_at_minimum() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
transfer( transfer(
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
&user_stake.stake_account, &validator_stake.stake_account,
1_000_001, 1_000_001,
) )
.await; .await;
@ -222,8 +222,8 @@ async fn fail_not_at_minimum() {
&payer, &payer,
&recent_blockhash, &recent_blockhash,
&new_authority, &new_authority,
&user_stake.stake_account, &validator_stake.stake_account,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
) )
.await .await
.unwrap() .unwrap()
@ -239,7 +239,7 @@ async fn fail_not_at_minimum() {
#[tokio::test] #[tokio::test]
async fn fail_double_remove() { async fn fail_double_remove() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
let new_authority = Pubkey::new_unique(); let new_authority = Pubkey::new_unique();
@ -249,8 +249,8 @@ async fn fail_double_remove() {
&payer, &payer,
&recent_blockhash, &recent_blockhash,
&new_authority, &new_authority,
&user_stake.stake_account, &validator_stake.stake_account,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
) )
.await; .await;
assert!(error.is_none()); assert!(error.is_none());
@ -263,8 +263,8 @@ async fn fail_double_remove() {
&payer, &payer,
&latest_blockhash, &latest_blockhash,
&new_authority, &new_authority,
&user_stake.stake_account, &validator_stake.stake_account,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
) )
.await .await
.unwrap(); .unwrap();
@ -285,7 +285,7 @@ async fn fail_double_remove() {
#[tokio::test] #[tokio::test]
async fn fail_wrong_staker() { async fn fail_wrong_staker() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
let malicious = Keypair::new(); let malicious = Keypair::new();
@ -299,8 +299,8 @@ async fn fail_wrong_staker() {
&stake_pool_accounts.withdraw_authority, &stake_pool_accounts.withdraw_authority,
&new_authority, &new_authority,
&stake_pool_accounts.validator_list.pubkey(), &stake_pool_accounts.validator_list.pubkey(),
&user_stake.stake_account, &validator_stake.stake_account,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
) )
.unwrap()], .unwrap()],
Some(&payer.pubkey()), Some(&payer.pubkey()),
@ -328,7 +328,7 @@ async fn fail_wrong_staker() {
#[tokio::test] #[tokio::test]
async fn fail_no_signature() { async fn fail_no_signature() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
let new_authority = Pubkey::new_unique(); let new_authority = Pubkey::new_unique();
@ -339,8 +339,8 @@ async fn fail_no_signature() {
AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false), AccountMeta::new_readonly(stake_pool_accounts.withdraw_authority, false),
AccountMeta::new_readonly(new_authority, false), AccountMeta::new_readonly(new_authority, false),
AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false), AccountMeta::new(stake_pool_accounts.validator_list.pubkey(), false),
AccountMeta::new(user_stake.stake_account, false), AccountMeta::new(validator_stake.stake_account, false),
AccountMeta::new_readonly(user_stake.transient_stake_account, false), AccountMeta::new_readonly(validator_stake.transient_stake_account, false),
AccountMeta::new_readonly(sysvar::clock::id(), false), AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(stake_program::id(), false), AccountMeta::new_readonly(stake_program::id(), false),
]; ];
@ -378,7 +378,7 @@ async fn fail_no_signature() {
#[tokio::test] #[tokio::test]
async fn fail_with_activating_transient_stake() { async fn fail_with_activating_transient_stake() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, user_stake) = let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await; setup().await;
// increase the validator stake // increase the validator stake
@ -387,8 +387,8 @@ async fn fail_with_activating_transient_stake() {
&mut banks_client, &mut banks_client,
&payer, &payer,
&recent_blockhash, &recent_blockhash,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
&user_stake.vote.pubkey(), &validator_stake.vote.pubkey(),
2_000_000_000, 2_000_000_000,
) )
.await; .await;
@ -401,8 +401,8 @@ async fn fail_with_activating_transient_stake() {
&payer, &payer,
&recent_blockhash, &recent_blockhash,
&new_authority, &new_authority,
&user_stake.stake_account, &validator_stake.stake_account,
&user_stake.transient_stake_account, &validator_stake.transient_stake_account,
) )
.await .await
.unwrap() .unwrap()
@ -419,6 +419,129 @@ async fn fail_with_activating_transient_stake() {
} }
} }
#[tokio::test]
async fn success_with_deactivating_transient_stake() {
let (mut banks_client, payer, recent_blockhash, stake_pool_accounts, validator_stake) =
setup().await;
let rent = banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let deposit_info = simple_deposit(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts,
&validator_stake,
TEST_STAKE_AMOUNT,
)
.await
.unwrap();
// increase the validator stake
let error = stake_pool_accounts
.decrease_validator_stake(
&mut banks_client,
&payer,
&recent_blockhash,
&validator_stake.stake_account,
&validator_stake.transient_stake_account,
TEST_STAKE_AMOUNT + stake_rent,
)
.await;
assert!(error.is_none());
let new_authority = Pubkey::new_unique();
let error = stake_pool_accounts
.remove_validator_from_pool(
&mut banks_client,
&payer,
&recent_blockhash,
&new_authority,
&validator_stake.stake_account,
&validator_stake.transient_stake_account,
)
.await;
assert!(error.is_none());
// fail deposit
let maybe_deposit = simple_deposit(
&mut banks_client,
&payer,
&recent_blockhash,
&stake_pool_accounts,
&validator_stake,
TEST_STAKE_AMOUNT,
)
.await;
assert!(maybe_deposit.is_none());
// fail withdraw
let user_stake_recipient = Keypair::new();
create_blank_stake_account(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake_recipient,
)
.await;
let new_authority = Pubkey::new_unique();
let error = stake_pool_accounts
.withdraw_stake(
&mut banks_client,
&payer,
&recent_blockhash,
&user_stake_recipient.pubkey(),
&deposit_info.pool_account.pubkey(),
&validator_stake.stake_account,
&new_authority,
1,
)
.await;
assert!(error.is_some());
// check validator has changed
let validator_list = get_account(
&mut banks_client,
&stake_pool_accounts.validator_list.pubkey(),
)
.await;
let validator_list =
try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
let expected_list = state::ValidatorList {
account_type: state::AccountType::ValidatorList,
max_validators: stake_pool_accounts.max_validators,
validators: vec![state::ValidatorStakeInfo {
status: state::StakeStatus::DeactivatingTransient,
vote_account_address: validator_stake.vote.pubkey(),
last_update_epoch: 0,
stake_lamports: TEST_STAKE_AMOUNT + stake_rent,
}],
};
assert_eq!(validator_list, expected_list);
// Update, should not change, no merges yet
let error = stake_pool_accounts
.update_all(
&mut banks_client,
&payer,
&recent_blockhash,
&[validator_stake.vote.pubkey()],
false,
)
.await;
assert!(error.is_none());
let validator_list = get_account(
&mut banks_client,
&stake_pool_accounts.validator_list.pubkey(),
)
.await;
let validator_list =
try_from_slice_unchecked::<state::ValidatorList>(validator_list.data.as_slice()).unwrap();
assert_eq!(validator_list, expected_list);
}
#[tokio::test] #[tokio::test]
async fn fail_not_updated_stake_pool() {} // TODO async fn fail_not_updated_stake_pool() {} // TODO

View File

@ -57,7 +57,8 @@ async fn setup() -> (
&validator_stake_account, &validator_stake_account,
TEST_STAKE_AMOUNT, TEST_STAKE_AMOUNT,
) )
.await; .await
.unwrap();
let tokens_to_burn = deposit_info.pool_tokens / 4; let tokens_to_burn = deposit_info.pool_tokens / 4;
@ -607,7 +608,8 @@ async fn fail_without_token_approval() {
&validator_stake_account, &validator_stake_account,
TEST_STAKE_AMOUNT, TEST_STAKE_AMOUNT,
) )
.await; .await
.unwrap();
let tokens_to_burn = deposit_info.pool_tokens / 4; let tokens_to_burn = deposit_info.pool_tokens / 4;
@ -675,7 +677,8 @@ async fn fail_with_low_delegation() {
&validator_stake_account, &validator_stake_account,
TEST_STAKE_AMOUNT, TEST_STAKE_AMOUNT,
) )
.await; .await
.unwrap();
let tokens_to_burn = deposit_info.pool_tokens / 4; let tokens_to_burn = deposit_info.pool_tokens / 4;
@ -819,7 +822,8 @@ async fn success_with_reserve() {
&validator_stake, &validator_stake,
deposit_lamports, deposit_lamports,
) )
.await; .await
.unwrap();
// decrease some stake // decrease some stake
let error = stake_pool_accounts let error = stake_pool_accounts