solana-program-library/stake-pool/program/tests/redelegate.rs

1130 lines
34 KiB
Rust

#![allow(clippy::integer_arithmetic)]
#![cfg(feature = "test-sbf")]
mod helpers;
use {
bincode::deserialize,
helpers::*,
solana_program::{
clock::Epoch, hash::Hash, instruction::InstructionError, pubkey::Pubkey, stake,
},
solana_program_test::*,
solana_sdk::{
signature::{Keypair, Signer},
stake::instruction::StakeError,
transaction::{Transaction, TransactionError},
},
spl_stake_pool::{
error::StakePoolError, find_ephemeral_stake_program_address,
find_transient_stake_program_address, id, instruction, MINIMUM_RESERVE_LAMPORTS,
},
};
async fn setup(
do_warp: bool,
) -> (
ProgramTestContext,
Hash,
StakePoolAccounts,
ValidatorStakeAccount,
ValidatorStakeAccount,
u64,
u64,
) {
let mut context = program_test().start_with_context().await;
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());
let current_minimum_delegation = stake_pool_get_minimum_delegation(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
)
.await;
let stake_pool_accounts = StakePoolAccounts::default();
stake_pool_accounts
.initialize_stake_pool(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
MINIMUM_RESERVE_LAMPORTS + current_minimum_delegation + stake_rent,
)
.await
.unwrap();
let source_validator_stake = simple_add_validator_to_pool(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&stake_pool_accounts,
None,
)
.await;
let destination_validator_stake = simple_add_validator_to_pool(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&stake_pool_accounts,
None,
)
.await;
let minimum_redelegate_lamports = current_minimum_delegation + stake_rent * 2;
simple_deposit_stake(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&stake_pool_accounts,
&source_validator_stake,
minimum_redelegate_lamports,
)
.await
.unwrap();
let mut slot = 0;
if do_warp {
slot = context.genesis_config().epoch_schedule.first_normal_slot;
context.warp_to_slot(slot).unwrap();
stake_pool_accounts
.update_all(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&[
source_validator_stake.vote.pubkey(),
destination_validator_stake.vote.pubkey(),
],
false,
)
.await;
}
let last_blockhash = context
.banks_client
.get_new_latest_blockhash(&context.last_blockhash)
.await
.unwrap();
(
context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
minimum_redelegate_lamports,
slot,
)
}
#[tokio::test]
async fn success() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
mut slot,
) = setup(true).await;
// Save validator stake
let pre_validator_stake_account = get_account(
&mut context.banks_client,
&source_validator_stake.stake_account,
)
.await;
// Save validator stake
let pre_destination_validator_stake_account = get_account(
&mut context.banks_client,
&destination_validator_stake.stake_account,
)
.await;
// Check no transient stake
let transient_account = context
.banks_client
.get_account(source_validator_stake.transient_stake_account)
.await
.unwrap();
assert!(transient_account.is_none());
let ephemeral_stake_seed = 100;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await;
assert!(error.is_none());
// Check validator stake account balance
let validator_stake_account = get_account(
&mut context.banks_client,
&source_validator_stake.stake_account,
)
.await;
let validator_stake_state =
deserialize::<stake::state::StakeState>(&validator_stake_account.data).unwrap();
assert_eq!(
pre_validator_stake_account.lamports - redelegate_lamports,
validator_stake_account.lamports
);
assert_eq!(
validator_stake_state
.delegation()
.unwrap()
.deactivation_epoch,
Epoch::MAX
);
// Check source transient stake account state and balance
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());
let source_transient_stake_account = get_account(
&mut context.banks_client,
&source_validator_stake.transient_stake_account,
)
.await;
let transient_stake_state =
deserialize::<stake::state::StakeState>(&source_transient_stake_account.data).unwrap();
assert_eq!(source_transient_stake_account.lamports, stake_rent);
let transient_delegation = transient_stake_state.delegation().unwrap();
assert_ne!(transient_delegation.deactivation_epoch, Epoch::MAX);
assert_eq!(transient_delegation.stake, redelegate_lamports - stake_rent);
// Check ephemeral account doesn't exist
let maybe_account = context
.banks_client
.get_account(ephemeral_stake)
.await
.unwrap();
assert!(maybe_account.is_none());
// Check destination transient stake account
let destination_transient_stake_account = get_account(
&mut context.banks_client,
&destination_validator_stake.transient_stake_account,
)
.await;
let transient_stake_state =
deserialize::<stake::state::StakeState>(&destination_transient_stake_account.data).unwrap();
assert_eq!(
destination_transient_stake_account.lamports,
redelegate_lamports - stake_rent
);
let transient_delegation = transient_stake_state.delegation().unwrap();
assert_eq!(transient_delegation.deactivation_epoch, Epoch::MAX);
assert_ne!(transient_delegation.activation_epoch, Epoch::MAX);
assert_eq!(
transient_delegation.stake,
redelegate_lamports - stake_rent * 2
);
// Check validator list
let validator_list = stake_pool_accounts
.get_validator_list(&mut context.banks_client)
.await;
let source_item = validator_list
.find(&source_validator_stake.vote.pubkey())
.unwrap();
assert_eq!(
source_item.active_stake_lamports,
validator_stake_account.lamports
);
assert_eq!(
source_item.transient_stake_lamports,
source_transient_stake_account.lamports
);
assert_eq!(
source_item.transient_seed_suffix,
source_validator_stake.transient_stake_seed
);
let destination_item = validator_list
.find(&destination_validator_stake.vote.pubkey())
.unwrap();
assert_eq!(
destination_item.transient_stake_lamports,
destination_transient_stake_account.lamports
);
assert_eq!(
destination_item.transient_seed_suffix,
destination_validator_stake.transient_stake_seed
);
// Warp forward and merge all
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
slot += slots_per_epoch;
context.warp_to_slot(slot).unwrap();
stake_pool_accounts
.update_all(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&[
source_validator_stake.vote.pubkey(),
destination_validator_stake.vote.pubkey(),
],
false,
)
.await;
// Check transient accounts are gone
let maybe_account = context
.banks_client
.get_account(destination_validator_stake.transient_stake_account)
.await
.unwrap();
assert!(maybe_account.is_none());
let maybe_account = context
.banks_client
.get_account(source_validator_stake.transient_stake_account)
.await
.unwrap();
assert!(maybe_account.is_none());
// Check validator list
let validator_list = stake_pool_accounts
.get_validator_list(&mut context.banks_client)
.await;
let source_item = validator_list
.find(&source_validator_stake.vote.pubkey())
.unwrap();
assert_eq!(
source_item.active_stake_lamports,
validator_stake_account.lamports
);
assert_eq!(source_item.transient_stake_lamports, 0);
let destination_item = validator_list
.find(&destination_validator_stake.vote.pubkey())
.unwrap();
assert_eq!(destination_item.transient_stake_lamports, 0);
assert_eq!(
destination_item.active_stake_lamports,
pre_destination_validator_stake_account.lamports + redelegate_lamports - stake_rent * 2
);
let post_destination_validator_stake_account = get_account(
&mut context.banks_client,
&destination_validator_stake.stake_account,
)
.await;
assert_eq!(
post_destination_validator_stake_account.lamports,
pre_destination_validator_stake_account.lamports + redelegate_lamports - stake_rent * 2
);
}
#[tokio::test]
async fn success_with_increasing_stake() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
mut slot,
) = setup(true).await;
// Save validator stake
let pre_validator_stake_account = get_account(
&mut context.banks_client,
&destination_validator_stake.stake_account,
)
.await;
let current_minimum_delegation = stake_pool_get_minimum_delegation(
&mut context.banks_client,
&context.payer,
&last_blockhash,
)
.await;
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());
let error = stake_pool_accounts
.increase_validator_stake(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
current_minimum_delegation,
destination_validator_stake.transient_stake_seed,
)
.await;
assert!(error.is_none());
let validator_list = stake_pool_accounts
.get_validator_list(&mut context.banks_client)
.await;
let destination_item = validator_list
.find(&destination_validator_stake.vote.pubkey())
.unwrap();
assert_eq!(
destination_item.transient_stake_lamports,
current_minimum_delegation + stake_rent
);
let pre_transient_stake_account = get_account(
&mut context.banks_client,
&destination_validator_stake.transient_stake_account,
)
.await;
assert_eq!(
pre_transient_stake_account.lamports,
current_minimum_delegation + stake_rent
);
let ephemeral_stake_seed = 10;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
// fail with incorrect transient stake derivation
let wrong_transient_stake_seed = destination_validator_stake
.transient_stake_seed
.wrapping_add(1);
let (wrong_transient_stake_account, _) = find_transient_stake_program_address(
&id(),
&destination_validator_stake.vote.pubkey(),
&stake_pool_accounts.stake_pool.pubkey(),
wrong_transient_stake_seed,
);
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&wrong_transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
wrong_transient_stake_seed,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::InvalidStakeAccountAddress as u32)
)
);
let last_blockhash = context
.banks_client
.get_new_latest_blockhash(&context.last_blockhash)
.await
.unwrap();
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await;
assert!(error.is_none());
// Check destination transient stake account
let destination_transient_stake_account = get_account(
&mut context.banks_client,
&destination_validator_stake.transient_stake_account,
)
.await;
let transient_stake_state =
deserialize::<stake::state::StakeState>(&destination_transient_stake_account.data).unwrap();
// stake rent cancels out
assert_eq!(
destination_transient_stake_account.lamports,
redelegate_lamports + current_minimum_delegation
);
let transient_delegation = transient_stake_state.delegation().unwrap();
assert_eq!(transient_delegation.deactivation_epoch, Epoch::MAX);
assert_ne!(transient_delegation.activation_epoch, Epoch::MAX);
assert_eq!(
transient_delegation.stake,
redelegate_lamports + current_minimum_delegation - stake_rent
);
// Check validator list
let validator_list = stake_pool_accounts
.get_validator_list(&mut context.banks_client)
.await;
let destination_item = validator_list
.find(&destination_validator_stake.vote.pubkey())
.unwrap();
assert_eq!(
destination_item.transient_stake_lamports,
destination_transient_stake_account.lamports
);
assert_eq!(
destination_item.transient_seed_suffix,
destination_validator_stake.transient_stake_seed
);
// Warp forward and merge all
let slots_per_epoch = context.genesis_config().epoch_schedule.slots_per_epoch;
slot += slots_per_epoch;
context.warp_to_slot(slot).unwrap();
stake_pool_accounts
.update_all(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&[
source_validator_stake.vote.pubkey(),
destination_validator_stake.vote.pubkey(),
],
false,
)
.await;
// Check transient account is gone
let maybe_account = context
.banks_client
.get_account(destination_validator_stake.transient_stake_account)
.await
.unwrap();
assert!(maybe_account.is_none());
// Check validator list
let validator_list = stake_pool_accounts
.get_validator_list(&mut context.banks_client)
.await;
let destination_item = validator_list
.find(&destination_validator_stake.vote.pubkey())
.unwrap();
assert_eq!(destination_item.transient_stake_lamports, 0);
// redelegate is smart enough to activate *everything*, so there's only one rent-exemption
// worth of inactive stake!
assert_eq!(
destination_item.active_stake_lamports,
pre_validator_stake_account.lamports + redelegate_lamports + current_minimum_delegation
- stake_rent
);
let post_validator_stake_account = get_account(
&mut context.banks_client,
&destination_validator_stake.stake_account,
)
.await;
assert_eq!(
post_validator_stake_account.lamports,
pre_validator_stake_account.lamports + redelegate_lamports + current_minimum_delegation
- stake_rent
);
}
#[tokio::test]
async fn fail_with_decreasing_stake() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
mut slot,
) = setup(false).await;
let current_minimum_delegation = stake_pool_get_minimum_delegation(
&mut context.banks_client,
&context.payer,
&last_blockhash,
)
.await;
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());
let minimum_decrease_lamports = current_minimum_delegation + stake_rent;
simple_deposit_stake(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&stake_pool_accounts,
&destination_validator_stake,
redelegate_lamports,
)
.await
.unwrap();
slot += context.genesis_config().epoch_schedule.first_normal_slot;
context.warp_to_slot(slot).unwrap();
stake_pool_accounts
.update_all(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&[
source_validator_stake.vote.pubkey(),
destination_validator_stake.vote.pubkey(),
],
false,
)
.await;
let last_blockhash = context
.banks_client
.get_new_latest_blockhash(&last_blockhash)
.await
.unwrap();
let error = stake_pool_accounts
.decrease_validator_stake(
&mut context.banks_client,
&context.payer,
&context.last_blockhash,
&destination_validator_stake.stake_account,
&destination_validator_stake.transient_stake_account,
minimum_decrease_lamports,
destination_validator_stake.transient_stake_seed,
)
.await;
assert!(error.is_none());
let ephemeral_stake_seed = 20;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::WrongStakeState as u32)
)
);
}
#[tokio::test]
async fn fail_with_wrong_withdraw_authority() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
_,
) = setup(true).await;
let ephemeral_stake_seed = 2;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let wrong_withdraw_authority = Pubkey::new_unique();
let transaction = Transaction::new_signed_with_payer(
&[instruction::redelegate(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.staker.pubkey(),
&wrong_withdraw_authority,
&stake_pool_accounts.validator_list.pubkey(),
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)],
Some(&context.payer.pubkey()),
&[&context.payer, &stake_pool_accounts.staker],
last_blockhash,
);
let error = context
.banks_client
.process_transaction(transaction)
.await
.err()
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::InvalidProgramAddress as u32)
)
);
}
#[tokio::test]
async fn fail_with_wrong_validator_list() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
_,
) = setup(true).await;
let ephemeral_stake_seed = 2;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let wrong_validator_list = Pubkey::new_unique();
let transaction = Transaction::new_signed_with_payer(
&[instruction::redelegate(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&stake_pool_accounts.staker.pubkey(),
&stake_pool_accounts.withdraw_authority,
&wrong_validator_list,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)],
Some(&context.payer.pubkey()),
&[&context.payer, &stake_pool_accounts.staker],
last_blockhash,
);
let error = context
.banks_client
.process_transaction(transaction)
.await
.err()
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::InvalidValidatorStakeList as u32)
)
);
}
#[tokio::test]
async fn fail_with_wrong_staker() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
_,
) = setup(true).await;
let ephemeral_stake_seed = 2;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let wrong_staker = Keypair::new();
let transaction = Transaction::new_signed_with_payer(
&[instruction::redelegate(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
&wrong_staker.pubkey(),
&stake_pool_accounts.withdraw_authority,
&stake_pool_accounts.validator_list.pubkey(),
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)],
Some(&context.payer.pubkey()),
&[&context.payer, &wrong_staker],
last_blockhash,
);
let error = context
.banks_client
.process_transaction(transaction)
.await
.err()
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::WrongStaker as u32)
)
);
}
#[tokio::test]
async fn fail_with_unknown_validator() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
_,
) = setup(true).await;
let unknown_validator_stake = create_unknown_validator_stake(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&stake_pool_accounts.stake_pool.pubkey(),
redelegate_lamports,
)
.await;
let ephemeral_stake_seed = 42;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&unknown_validator_stake.transient_stake_account,
&unknown_validator_stake.stake_account,
&unknown_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
unknown_validator_stake.transient_stake_seed,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::ValidatorNotFound as u32)
)
);
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&unknown_validator_stake.stake_account,
&unknown_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
unknown_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::ValidatorNotFound as u32)
)
);
}
#[tokio::test]
async fn fail_redelegate_twice() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
mut slot,
) = setup(false).await;
simple_deposit_stake(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&stake_pool_accounts,
&source_validator_stake,
redelegate_lamports,
)
.await
.unwrap();
slot += context.genesis_config().epoch_schedule.first_normal_slot;
context.warp_to_slot(slot).unwrap();
stake_pool_accounts
.update_all(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&[
source_validator_stake.vote.pubkey(),
destination_validator_stake.vote.pubkey(),
],
false,
)
.await;
let last_blockhash = context
.banks_client
.get_new_latest_blockhash(&last_blockhash)
.await
.unwrap();
let ephemeral_stake_seed = 100;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await;
assert!(error.is_none());
let last_blockhash = context
.banks_client
.get_new_latest_blockhash(&last_blockhash)
.await
.unwrap();
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakePoolError::TransientAccountInUse as u32)
)
);
}
#[tokio::test]
async fn fail_with_small_lamport_amount() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
redelegate_lamports,
_,
) = setup(true).await;
let ephemeral_stake_seed = 7_000;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
redelegate_lamports - 1,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(
0,
InstructionError::Custom(StakeError::InsufficientDelegation as u32)
)
);
}
#[tokio::test]
async fn fail_drain_source_account() {
let (
mut context,
last_blockhash,
stake_pool_accounts,
source_validator_stake,
destination_validator_stake,
_,
_,
) = setup(true).await;
let validator_stake_account = get_account(
&mut context.banks_client,
&source_validator_stake.stake_account,
)
.await;
let ephemeral_stake_seed = 2;
let ephemeral_stake = find_ephemeral_stake_program_address(
&id(),
&stake_pool_accounts.stake_pool.pubkey(),
ephemeral_stake_seed,
)
.0;
let error = stake_pool_accounts
.redelegate(
&mut context.banks_client,
&context.payer,
&last_blockhash,
&source_validator_stake.stake_account,
&source_validator_stake.transient_stake_account,
&ephemeral_stake,
&destination_validator_stake.transient_stake_account,
&destination_validator_stake.stake_account,
&destination_validator_stake.vote.pubkey(),
validator_stake_account.lamports,
source_validator_stake.transient_stake_seed,
ephemeral_stake_seed,
destination_validator_stake.transient_stake_seed,
)
.await
.unwrap()
.unwrap();
assert_eq!(
error,
TransactionError::InstructionError(0, InstructionError::InsufficientFunds)
);
}