diff --git a/stake-pool/js/src/index.ts b/stake-pool/js/src/index.ts index 27b75f2fd0c..a3f31d4b841 100644 --- a/stake-pool/js/src/index.ts +++ b/stake-pool/js/src/index.ts @@ -72,17 +72,6 @@ export interface StakePoolAccounts { validatorList: ValidatorListAccount | undefined; } -interface RedelegateProps { - connection: Connection; - stakePoolAddress: PublicKey; - sourceVoteAccount: PublicKey; - destinationVoteAccount: PublicKey; - sourceTransientStakeSeed: number | BN; - destinationTransientStakeSeed: number | BN; - ephemeralStakeSeed: number | BN; - lamports: number | BN; -} - /** * Retrieves and deserializes a StakePool account using a web3js connection and the stake pool address. * @param connection: An active web3js connection. @@ -1160,86 +1149,6 @@ export async function stakePoolInfo(connection: Connection, stakePoolAddress: Pu }; } -/** - * Creates instructions required to redelegate stake. - */ -export async function redelegate(props: RedelegateProps) { - const { - connection, - stakePoolAddress, - sourceVoteAccount, - sourceTransientStakeSeed, - destinationVoteAccount, - destinationTransientStakeSeed, - ephemeralStakeSeed, - lamports, - } = props; - const stakePool = await getStakePoolAccount(connection, stakePoolAddress); - - const stakePoolWithdrawAuthority = await findWithdrawAuthorityProgramAddress( - STAKE_POOL_PROGRAM_ID, - stakePoolAddress, - ); - - const sourceValidatorStake = await findStakeProgramAddress( - STAKE_POOL_PROGRAM_ID, - sourceVoteAccount, - stakePoolAddress, - ); - - const sourceTransientStake = await findTransientStakeProgramAddress( - STAKE_POOL_PROGRAM_ID, - sourceVoteAccount, - stakePoolAddress, - new BN(sourceTransientStakeSeed), - ); - - const destinationValidatorStake = await findStakeProgramAddress( - STAKE_POOL_PROGRAM_ID, - destinationVoteAccount, - stakePoolAddress, - ); - - const destinationTransientStake = await findTransientStakeProgramAddress( - STAKE_POOL_PROGRAM_ID, - destinationVoteAccount, - stakePoolAddress, - new BN(destinationTransientStakeSeed), - ); - - const ephemeralStake = await findEphemeralStakeProgramAddress( - STAKE_POOL_PROGRAM_ID, - stakePoolAddress, - new BN(ephemeralStakeSeed), - ); - - const instructions: TransactionInstruction[] = []; - - instructions.push( - StakePoolInstruction.redelegate({ - stakePool: stakePool.pubkey, - staker: stakePool.account.data.staker, - validatorList: stakePool.account.data.validatorList, - reserveStake: stakePool.account.data.reserveStake, - stakePoolWithdrawAuthority, - ephemeralStake, - ephemeralStakeSeed, - sourceValidatorStake, - sourceTransientStake, - sourceTransientStakeSeed, - destinationValidatorStake, - destinationTransientStake, - destinationTransientStakeSeed, - validator: destinationVoteAccount, - lamports, - }), - ); - - return { - instructions, - }; -} - /** * Creates instructions required to create pool token metadata. */ diff --git a/stake-pool/js/src/instructions.ts b/stake-pool/js/src/instructions.ts index 970d673635d..e792dd12687 100644 --- a/stake-pool/js/src/instructions.ts +++ b/stake-pool/js/src/instructions.ts @@ -11,7 +11,6 @@ import { import * as BufferLayout from '@solana/buffer-layout'; import { TOKEN_PROGRAM_ID } from '@solana/spl-token'; import { InstructionType, encodeData, decodeData } from './utils'; -import BN from 'bn.js'; import { METADATA_MAX_NAME_LENGTH, METADATA_MAX_SYMBOL_LENGTH, @@ -175,19 +174,7 @@ export const STAKE_POOL_INSTRUCTION_LAYOUTS: { }, Redelegate: { index: 22, - layout: BufferLayout.struct([ - BufferLayout.u8('instruction'), - /// Amount of lamports to redelegate - BufferLayout.ns64('lamports'), - /// Seed used to create source transient stake account - BufferLayout.ns64('sourceTransientStakeSeed'), - /// Seed used to create destination ephemeral account. - BufferLayout.ns64('ephemeralStakeSeed'), - /// Seed used to create destination transient stake account. If there is - /// already transient stake, this must match the current seed, otherwise - /// it can be anything - BufferLayout.ns64('destinationTransientStakeSeed'), - ]), + layout: BufferLayout.struct([BufferLayout.u8('instruction')]), }, }); @@ -340,30 +327,6 @@ export type DepositSolParams = { lamports: number; }; -export type RedelegateParams = { - stakePool: PublicKey; - staker: PublicKey; - stakePoolWithdrawAuthority: PublicKey; - validatorList: PublicKey; - reserveStake: PublicKey; - sourceValidatorStake: PublicKey; - sourceTransientStake: PublicKey; - ephemeralStake: PublicKey; - destinationTransientStake: PublicKey; - destinationValidatorStake: PublicKey; - validator: PublicKey; - // Amount of lamports to redelegate - lamports: number | BN; - // Seed used to create source transient stake account - sourceTransientStakeSeed: number | BN; - // Seed used to create destination ephemeral account - ephemeralStakeSeed: number | BN; - // Seed used to create destination transient stake account. If there is - // already transient stake, this must match the current seed, otherwise - // it can be anything - destinationTransientStakeSeed: number | BN; -}; - export type CreateTokenMetadataParams = { stakePool: PublicKey; manager: PublicKey; @@ -984,62 +947,6 @@ export class StakePoolInstruction { }); } - /** - * Creates `Redelegate` instruction (rebalance from one validator account to another) - * @param params - */ - static redelegate(params: RedelegateParams): TransactionInstruction { - const { - stakePool, - staker, - stakePoolWithdrawAuthority, - validatorList, - reserveStake, - sourceValidatorStake, - sourceTransientStake, - ephemeralStake, - destinationTransientStake, - destinationValidatorStake, - validator, - lamports, - sourceTransientStakeSeed, - ephemeralStakeSeed, - destinationTransientStakeSeed, - } = params; - - const keys = [ - { pubkey: stakePool, isSigner: false, isWritable: false }, - { pubkey: staker, isSigner: true, isWritable: false }, - { pubkey: stakePoolWithdrawAuthority, isSigner: false, isWritable: false }, - { pubkey: validatorList, isSigner: false, isWritable: true }, - { pubkey: reserveStake, isSigner: false, isWritable: true }, - { pubkey: sourceValidatorStake, isSigner: false, isWritable: true }, - { pubkey: sourceTransientStake, isSigner: false, isWritable: true }, - { pubkey: ephemeralStake, isSigner: false, isWritable: true }, - { pubkey: destinationTransientStake, isSigner: false, isWritable: true }, - { pubkey: destinationValidatorStake, isSigner: false, isWritable: false }, - { pubkey: validator, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: SYSVAR_STAKE_HISTORY_PUBKEY, isSigner: false, isWritable: false }, - { pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - { pubkey: StakeProgram.programId, isSigner: false, isWritable: false }, - ]; - - const data = encodeData(STAKE_POOL_INSTRUCTION_LAYOUTS.Redelegate, { - lamports, - sourceTransientStakeSeed, - ephemeralStakeSeed, - destinationTransientStakeSeed, - }); - - return new TransactionInstruction({ - programId: STAKE_POOL_PROGRAM_ID, - keys, - data, - }); - } - /** * Creates an instruction to create metadata * using the mpl token metadata program for the pool token diff --git a/stake-pool/js/test/instructions.test.ts b/stake-pool/js/test/instructions.test.ts index 490e777fb87..c3e9b7c9969 100644 --- a/stake-pool/js/test/instructions.test.ts +++ b/stake-pool/js/test/instructions.test.ts @@ -27,7 +27,6 @@ import { depositSol, withdrawSol, withdrawStake, - redelegate, getStakeAccount, createPoolTokenMetadata, updatePoolTokenMetadata, @@ -498,31 +497,6 @@ describe('StakePoolProgram', () => { }); }); - describe('redelegation', () => { - it('should call successfully', async () => { - const data = { - connection, - stakePoolAddress, - sourceVoteAccount: PublicKey.default, - sourceTransientStakeSeed: 10, - destinationVoteAccount: PublicKey.default, - destinationTransientStakeSeed: 20, - ephemeralStakeSeed: 100, - lamports: 100, - }; - const res = await redelegate(data); - - const decodedData = STAKE_POOL_INSTRUCTION_LAYOUTS.Redelegate.layout.decode( - res.instructions[0].data, - ); - - expect(decodedData.instruction).toBe(22); - expect(decodedData.lamports).toBe(data.lamports); - expect(decodedData.sourceTransientStakeSeed).toBe(data.sourceTransientStakeSeed); - expect(decodedData.destinationTransientStakeSeed).toBe(data.destinationTransientStakeSeed); - expect(decodedData.ephemeralStakeSeed).toBe(data.ephemeralStakeSeed); - }); - }); describe('createPoolTokenMetadata', () => { it('should create pool token metadata', async () => { connection.getAccountInfo = jest.fn(async (pubKey: PublicKey) => { diff --git a/stake-pool/program/src/instruction.rs b/stake-pool/program/src/instruction.rs index 313afd93015..5b370a93808 100644 --- a/stake-pool/program/src/instruction.rs +++ b/stake-pool/program/src/instruction.rs @@ -1,5 +1,8 @@ //! Instruction types +// Remove the following `allow` when `Redelegate` is removed, required to avoid +// warnings from uses of deprecated types during trait derivations. +#![allow(deprecated)] #![allow(clippy::too_many_arguments)] use { @@ -599,6 +602,10 @@ pub enum StakePoolInstruction { /// 13. `[]` Stake Config sysvar /// 14. `[]` System program /// 15. `[]` Stake program + #[deprecated( + since = "2.0.0", + note = "The stake redelegate instruction used in this will not be enabled." + )] Redelegate { /// Amount of lamports to redelegate #[allow(dead_code)] // but it's not @@ -1051,6 +1058,10 @@ pub fn increase_additional_validator_stake( /// Creates `Redelegate` instruction (rebalance from one validator account to /// another) +#[deprecated( + since = "2.0.0", + note = "The stake redelegate instruction used in this will not be enabled." +)] pub fn redelegate( program_id: &Pubkey, stake_pool: &Pubkey, diff --git a/stake-pool/program/src/processor.rs b/stake-pool/program/src/processor.rs index 893346ebce8..24887f26f25 100644 --- a/stake-pool/program/src/processor.rs +++ b/stake-pool/program/src/processor.rs @@ -308,35 +308,6 @@ fn check_validator_stake_account( Ok(()) } -/// Checks if a transient stake account is valid, which means that it's usable -/// by the pool and delegated to the expected validator. These conditions can be -/// violated if a validator was force destaked during a cluster restart. -fn check_transient_stake_account( - stake_account_info: &AccountInfo, - program_id: &Pubkey, - stake_pool: &Pubkey, - withdraw_authority: &Pubkey, - vote_account_address: &Pubkey, - seed: u64, - lockup: &stake::state::Lockup, -) -> Result<(), ProgramError> { - check_account_owner(stake_account_info, &stake::program::id())?; - check_transient_stake_address( - program_id, - stake_pool, - stake_account_info.key, - vote_account_address, - seed, - )?; - check_stake_state( - stake_account_info, - withdraw_authority, - vote_account_address, - lockup, - )?; - Ok(()) -} - /// Create a stake account on a PDA without transferring lamports fn create_stake_account( stake_account_info: AccountInfo<'_>, @@ -592,41 +563,6 @@ impl Processor { ) } - /// Issue stake::instruction::redelegate instruction to redelegate stake - #[allow(clippy::too_many_arguments)] - fn stake_redelegate<'a>( - stake_pool: &Pubkey, - source_account: AccountInfo<'a>, - authority: AccountInfo<'a>, - authority_type: &[u8], - bump_seed: u8, - destination_account: AccountInfo<'a>, - vote_account: AccountInfo<'a>, - stake_config: AccountInfo<'a>, - ) -> Result<(), ProgramError> { - let authority_signature_seeds = [stake_pool.as_ref(), authority_type, &[bump_seed]]; - let signers = &[&authority_signature_seeds[..]]; - - let redelegate_instruction = &stake::instruction::redelegate( - source_account.key, - authority.key, - vote_account.key, - destination_account.key, - )[2]; - - invoke_signed( - redelegate_instruction, - &[ - source_account, - destination_account, - vote_account, - stake_config, - authority, - ], - signers, - ) - } - /// Issue a spl_token `Burn` instruction. #[allow(clippy::too_many_arguments)] fn token_burn<'a>( @@ -1842,346 +1778,6 @@ impl Processor { Ok(()) } - /// Processes `Redelegate` instruction. - #[inline(never)] // needed due to stack size violation - fn process_redelegate( - program_id: &Pubkey, - accounts: &[AccountInfo], - lamports: u64, - source_transient_stake_seed: u64, - ephemeral_stake_seed: u64, - destination_transient_stake_seed: u64, - ) -> ProgramResult { - let account_info_iter = &mut accounts.iter(); - let stake_pool_info = next_account_info(account_info_iter)?; - let staker_info = next_account_info(account_info_iter)?; - let withdraw_authority_info = next_account_info(account_info_iter)?; - let validator_list_info = next_account_info(account_info_iter)?; - let reserve_stake_info = next_account_info(account_info_iter)?; - let source_validator_stake_account_info = next_account_info(account_info_iter)?; - let source_transient_stake_account_info = next_account_info(account_info_iter)?; - let ephemeral_stake_account_info = next_account_info(account_info_iter)?; - let destination_transient_stake_account_info = next_account_info(account_info_iter)?; - let destination_validator_stake_account_info = next_account_info(account_info_iter)?; - let validator_vote_account_info = next_account_info(account_info_iter)?; - let clock_info = next_account_info(account_info_iter)?; - let clock = &Clock::from_account_info(clock_info)?; - let stake_history_info = next_account_info(account_info_iter)?; - let stake_config_info = next_account_info(account_info_iter)?; - let system_program_info = next_account_info(account_info_iter)?; - let stake_program_info = next_account_info(account_info_iter)?; - - check_system_program(system_program_info.key)?; - check_stake_program(stake_program_info.key)?; - check_account_owner(stake_pool_info, program_id)?; - - let stake_pool = try_from_slice_unchecked::(&stake_pool_info.data.borrow())?; - if !stake_pool.is_valid() { - msg!("Expected valid stake pool"); - return Err(StakePoolError::InvalidState.into()); - } - - stake_pool.check_authority_withdraw( - withdraw_authority_info.key, - program_id, - stake_pool_info.key, - )?; - stake_pool.check_staker(staker_info)?; - - if stake_pool.last_update_epoch < clock.epoch { - return Err(StakePoolError::StakeListAndPoolOutOfDate.into()); - } - - stake_pool.check_validator_list(validator_list_info)?; - check_account_owner(validator_list_info, program_id)?; - stake_pool.check_reserve_stake(reserve_stake_info)?; - - let mut validator_list_data = validator_list_info.data.borrow_mut(); - let (header, mut validator_list) = - ValidatorListHeader::deserialize_vec(&mut validator_list_data)?; - if !header.is_valid() { - return Err(StakePoolError::InvalidState.into()); - } - - let rent = Rent::get()?; - let stake_space = std::mem::size_of::(); - let stake_rent = rent.minimum_balance(stake_space); - let stake_minimum_delegation = stake::tools::get_minimum_delegation()?; - let current_minimum_delegation = minimum_delegation(stake_minimum_delegation); - - // check that we're redelegating enough - { - // redelegation requires that the source account maintains rent exemption and - // that the destination account has rent-exemption and minimum - // delegation - let minimum_redelegation_lamports = - current_minimum_delegation.saturating_add(stake_rent); - if lamports < minimum_redelegation_lamports { - msg!( - "Need more than {} lamports for redelegated stake and transient stake to meet minimum delegation requirement, {} provided", - minimum_redelegation_lamports, - lamports - ); - return Err(ProgramError::Custom( - stake::instruction::StakeError::InsufficientDelegation as u32, - )); - } - - // check that we're not draining the source account - let current_minimum_lamports = stake_rent.saturating_add(current_minimum_delegation); - if source_validator_stake_account_info - .lamports() - .saturating_sub(lamports) - < current_minimum_lamports - { - let max_split_amount = source_validator_stake_account_info - .lamports() - .saturating_sub(current_minimum_lamports); - msg!( - "Source stake does not have {} lamports for redelegation, must be {} at most", - lamports, - max_split_amount, - ); - return Err(ProgramError::InsufficientFunds); - } - } - - // check source account state - let (_, stake) = get_stake_state(source_validator_stake_account_info)?; - let vote_account_address = stake.delegation.voter_pubkey; - { - let maybe_validator_stake_info = - validator_list.find_mut::(|x| { - ValidatorStakeInfo::memcmp_pubkey(x, &vote_account_address) - }); - if maybe_validator_stake_info.is_none() { - msg!( - "Source vote account {} not found in stake pool", - vote_account_address - ); - return Err(StakePoolError::ValidatorNotFound.into()); - } - let validator_stake_info = maybe_validator_stake_info.unwrap(); - check_validator_stake_address( - program_id, - stake_pool_info.key, - source_validator_stake_account_info.key, - &vote_account_address, - NonZeroU32::new(validator_stake_info.validator_seed_suffix.into()), - )?; - if u64::from(validator_stake_info.transient_stake_lamports) > 0 { - return Err(StakePoolError::TransientAccountInUse.into()); - } - if validator_stake_info.status != StakeStatus::Active.into() { - msg!("Validator is marked for removal and no longer allows redelegation"); - return Err(StakePoolError::ValidatorNotFound.into()); - } - validator_stake_info.active_stake_lamports = - u64::from(validator_stake_info.active_stake_lamports) - .checked_sub(lamports) - .ok_or(StakePoolError::CalculationFailure)? - .into(); - validator_stake_info.transient_stake_lamports = stake_rent.into(); - validator_stake_info.transient_seed_suffix = source_transient_stake_seed.into(); - } - - // split from source, into source transient - { - // check transient account - let source_transient_stake_bump_seed = check_transient_stake_address( - program_id, - stake_pool_info.key, - source_transient_stake_account_info.key, - &vote_account_address, - source_transient_stake_seed, - )?; - let source_transient_stake_account_signer_seeds: &[&[_]] = &[ - TRANSIENT_STAKE_SEED_PREFIX, - vote_account_address.as_ref(), - stake_pool_info.key.as_ref(), - &source_transient_stake_seed.to_le_bytes(), - &[source_transient_stake_bump_seed], - ]; - - create_stake_account( - source_transient_stake_account_info.clone(), - source_transient_stake_account_signer_seeds, - stake_space, - )?; - - // if needed, pre-fund the rent-exempt reserve from the reserve stake - let required_lamports_for_rent_exemption = - stake_rent.saturating_sub(source_transient_stake_account_info.lamports()); - if required_lamports_for_rent_exemption > 0 { - if required_lamports_for_rent_exemption >= reserve_stake_info.lamports() { - return Err(StakePoolError::ReserveDepleted.into()); - } - Self::stake_withdraw( - stake_pool_info.key, - reserve_stake_info.clone(), - withdraw_authority_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.stake_withdraw_bump_seed, - source_transient_stake_account_info.clone(), - clock_info.clone(), - stake_history_info.clone(), - required_lamports_for_rent_exemption, - )?; - } - Self::stake_split( - stake_pool_info.key, - source_validator_stake_account_info.clone(), - withdraw_authority_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.stake_withdraw_bump_seed, - lamports, - source_transient_stake_account_info.clone(), - )?; - } - - // redelegate from source transient to ephemeral - { - let ephemeral_stake_bump_seed = check_ephemeral_stake_address( - program_id, - stake_pool_info.key, - ephemeral_stake_account_info.key, - ephemeral_stake_seed, - )?; - let ephemeral_stake_account_signer_seeds: &[&[_]] = &[ - EPHEMERAL_STAKE_SEED_PREFIX, - stake_pool_info.key.as_ref(), - &ephemeral_stake_seed.to_le_bytes(), - &[ephemeral_stake_bump_seed], - ]; - create_stake_account( - ephemeral_stake_account_info.clone(), - ephemeral_stake_account_signer_seeds, - stake_space, - )?; - Self::stake_redelegate( - stake_pool_info.key, - source_transient_stake_account_info.clone(), - withdraw_authority_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.stake_withdraw_bump_seed, - ephemeral_stake_account_info.clone(), - validator_vote_account_info.clone(), - stake_config_info.clone(), - )?; - } - - { - // check destination stake and transient stake accounts - let vote_account_address = validator_vote_account_info.key; - let maybe_validator_stake_info = - validator_list.find_mut::(|x| { - ValidatorStakeInfo::memcmp_pubkey(x, vote_account_address) - }); - if maybe_validator_stake_info.is_none() { - msg!( - "Destination vote account {} not found in stake pool", - vote_account_address - ); - return Err(StakePoolError::ValidatorNotFound.into()); - } - let validator_stake_info = maybe_validator_stake_info.unwrap(); - check_validator_stake_account( - destination_validator_stake_account_info, - program_id, - stake_pool_info.key, - withdraw_authority_info.key, - vote_account_address, - validator_stake_info.validator_seed_suffix.into(), - &stake_pool.lockup, - )?; - if validator_stake_info.status != StakeStatus::Active.into() { - msg!( - "Destination validator is marked for removal and no longer allows redelegation" - ); - return Err(StakePoolError::ValidatorNotFound.into()); - } - let transient_account_exists = - u64::from(validator_stake_info.transient_stake_lamports) > 0; - validator_stake_info.transient_stake_lamports = - u64::from(validator_stake_info.transient_stake_lamports) - .checked_add(lamports) - .ok_or(StakePoolError::CalculationFailure)? - .into(); - - if transient_account_exists { - // if transient stake exists, make sure it's the right one and that it's - // usable by the pool - if u64::from(validator_stake_info.transient_seed_suffix) - != destination_transient_stake_seed - { - msg!("Provided seed {} does not match current seed {} for transient stake account", - destination_transient_stake_seed, - u64::from(validator_stake_info.transient_seed_suffix) - ); - return Err(StakePoolError::InvalidStakeAccountAddress.into()); - } - check_transient_stake_account( - destination_transient_stake_account_info, - program_id, - stake_pool_info.key, - withdraw_authority_info.key, - vote_account_address, - destination_transient_stake_seed, - &stake_pool.lockup, - )?; - check_if_stake_activating( - destination_transient_stake_account_info, - vote_account_address, - clock.epoch, - )?; - Self::stake_merge( - stake_pool_info.key, - ephemeral_stake_account_info.clone(), - withdraw_authority_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.stake_withdraw_bump_seed, - destination_transient_stake_account_info.clone(), - clock_info.clone(), - stake_history_info.clone(), - )?; - } else { - // otherwise, create the new account and split into it - let destination_transient_stake_bump_seed = check_transient_stake_address( - program_id, - stake_pool_info.key, - destination_transient_stake_account_info.key, - vote_account_address, - destination_transient_stake_seed, - )?; - let destination_transient_stake_account_signer_seeds: &[&[_]] = &[ - TRANSIENT_STAKE_SEED_PREFIX, - vote_account_address.as_ref(), - stake_pool_info.key.as_ref(), - &destination_transient_stake_seed.to_le_bytes(), - &[destination_transient_stake_bump_seed], - ]; - create_stake_account( - destination_transient_stake_account_info.clone(), - destination_transient_stake_account_signer_seeds, - stake_space, - )?; - Self::stake_split( - stake_pool_info.key, - ephemeral_stake_account_info.clone(), - withdraw_authority_info.clone(), - AUTHORITY_WITHDRAW, - stake_pool.stake_withdraw_bump_seed, - lamports, - destination_transient_stake_account_info.clone(), - )?; - validator_stake_info.transient_seed_suffix = - destination_transient_stake_seed.into(); - } - } - - Ok(()) - } - /// Process `SetPreferredValidator` instruction #[inline(never)] // needed due to stack size violation fn process_set_preferred_validator( @@ -4012,21 +3608,10 @@ impl Processor { msg!("Instruction: UpdateTokenMetadata"); Self::process_update_pool_token_metadata(program_id, accounts, name, symbol, uri) } - StakePoolInstruction::Redelegate { - lamports, - source_transient_stake_seed, - ephemeral_stake_seed, - destination_transient_stake_seed, - } => { - msg!("Instruction: Redelegate"); - Self::process_redelegate( - program_id, - accounts, - lamports, - source_transient_stake_seed, - ephemeral_stake_seed, - destination_transient_stake_seed, - ) + #[allow(deprecated)] + StakePoolInstruction::Redelegate { .. } => { + msg!("Instruction: Redelegate will not be enabled"); + Err(ProgramError::InvalidInstructionData) } StakePoolInstruction::DepositStakeWithSlippage { minimum_pool_tokens_out, diff --git a/stake-pool/program/tests/helpers/mod.rs b/stake-pool/program/tests/helpers/mod.rs index eb785ff6384..89baa021502 100644 --- a/stake-pool/program/tests/helpers/mod.rs +++ b/stake-pool/program/tests/helpers/mod.rs @@ -1942,54 +1942,6 @@ impl StakePoolAccounts { } } - #[allow(clippy::too_many_arguments)] - pub async fn redelegate( - &self, - banks_client: &mut BanksClient, - payer: &Keypair, - recent_blockhash: &Hash, - source_validator_stake: &Pubkey, - source_transient_stake: &Pubkey, - ephemeral_stake: &Pubkey, - destination_transient_stake: &Pubkey, - destination_validator_stake: &Pubkey, - validator: &Pubkey, - lamports: u64, - source_transient_stake_seed: u64, - ephemeral_stake_seed: u64, - destination_transient_stake_seed: u64, - ) -> Option { - let transaction = Transaction::new_signed_with_payer( - &[instruction::redelegate( - &id(), - &self.stake_pool.pubkey(), - &self.staker.pubkey(), - &self.withdraw_authority, - &self.validator_list.pubkey(), - &self.reserve_stake.pubkey(), - source_validator_stake, - source_transient_stake, - ephemeral_stake, - destination_transient_stake, - destination_validator_stake, - validator, - lamports, - source_transient_stake_seed, - ephemeral_stake_seed, - destination_transient_stake_seed, - )], - Some(&payer.pubkey()), - &[payer, &self.staker], - *recent_blockhash, - ); - #[allow(clippy::useless_conversion)] // Remove during upgrade to 1.10 - banks_client - .process_transaction(transaction) - .await - .map_err(|e| e.into()) - .err() - } - pub async fn set_preferred_validator( &self, banks_client: &mut BanksClient, diff --git a/stake-pool/program/tests/redelegate.rs b/stake-pool/program/tests/redelegate.rs deleted file mode 100644 index 3256eef1155..00000000000 --- a/stake-pool/program/tests/redelegate.rs +++ /dev/null @@ -1,1213 +0,0 @@ -#![allow(clippy::arithmetic_side_effects)] -#![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::()); - 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 * 2, - ) - .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; - 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 = 1; - 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, - 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(), "{:?}", error); - - // 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::(&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::()); - - let source_transient_stake_account = get_account( - &mut context.banks_client, - &source_validator_stake.transient_stake_account, - ) - .await; - let transient_stake_state = - deserialize::(&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); - - // Check reserve stake - let current_minimum_delegation = stake_pool_get_minimum_delegation( - &mut context.banks_client, - &context.payer, - &last_blockhash, - ) - .await; - let reserve_lamports = MINIMUM_RESERVE_LAMPORTS + current_minimum_delegation + stake_rent * 2; - let reserve_stake_account = get_account( - &mut context.banks_client, - &stake_pool_accounts.reserve_stake.pubkey(), - ) - .await; - assert_eq!(reserve_stake_account.lamports, reserve_lamports); - - // 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::(&destination_transient_stake_account.data) - .unwrap(); - assert_eq!( - destination_transient_stake_account.lamports, - redelegate_lamports - ); - 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); - - // 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!( - u64::from(source_item.active_stake_lamports), - validator_stake_account.lamports - ); - assert_eq!( - u64::from(source_item.transient_stake_lamports), - source_transient_stake_account.lamports - ); - assert_eq!( - u64::from(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!( - u64::from(destination_item.transient_stake_lamports), - destination_transient_stake_account.lamports - ); - assert_eq!( - u64::from(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, - 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!( - u64::from(source_item.active_stake_lamports), - validator_stake_account.lamports - ); - assert_eq!(u64::from(source_item.transient_stake_lamports), 0); - - let destination_item = validator_list - .find(&destination_validator_stake.vote.pubkey()) - .unwrap(); - assert_eq!(u64::from(destination_item.transient_stake_lamports), 0); - assert_eq!( - u64::from(destination_item.active_stake_lamports), - pre_destination_validator_stake_account.lamports + redelegate_lamports - stake_rent - ); - 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 - ); - - // Check reserve stake, which has claimed back all rent-exempt reserves - let reserve_stake_account = get_account( - &mut context.banks_client, - &stake_pool_accounts.reserve_stake.pubkey(), - ) - .await; - assert_eq!( - reserve_stake_account.lamports, - reserve_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::()); - - 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(), "{:?}", error); - - 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!( - u64::from(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(), "{:?}", error); - - // 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::(&destination_transient_stake_account.data) - .unwrap(); - assert_eq!( - destination_transient_stake_account.lamports, - redelegate_lamports + current_minimum_delegation + 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 + current_minimum_delegation - ); - - // Check reserve stake - let reserve_stake_account = get_account( - &mut context.banks_client, - &stake_pool_accounts.reserve_stake.pubkey(), - ) - .await; - assert_eq!(reserve_stake_account.lamports, 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!( - u64::from(destination_item.transient_stake_lamports), - destination_transient_stake_account.lamports - ); - assert_eq!( - u64::from(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, - 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!(u64::from(destination_item.transient_stake_lamports), 0); - // redelegate is smart enough to activate *everything*, so there's no - // rent-exemption worth of inactive stake! - assert_eq!( - u64::from(destination_item.active_stake_lamports), - pre_validator_stake_account.lamports + redelegate_lamports + current_minimum_delegation - ); - 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 - ); - - // Check reserve has claimed back rent-exempt reserve from both transient - // accounts - let reserve_stake_account = get_account( - &mut context.banks_client, - &stake_pool_accounts.reserve_stake.pubkey(), - ) - .await; - assert_eq!(reserve_stake_account.lamports, stake_rent * 3); -} - -#[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::()); - 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, - false, - ) - .await; - - let last_blockhash = context - .banks_client - .get_new_latest_blockhash(&last_blockhash) - .await - .unwrap(); - - let error = stake_pool_accounts - .decrease_validator_stake_either( - &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, - DecreaseInstruction::Reserve, - ) - .await; - assert!(error.is_none(), "{:?}", error); - - 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::WrongStakeStake 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(), - &stake_pool_accounts.reserve_stake.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, - &stake_pool_accounts.reserve_stake.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::InvalidValidatorStakeList as u32) - ) - ); -} - -#[tokio::test] -async fn fail_with_wrong_reserve() { - 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_reserve = Keypair::new(); - 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, - &stake_pool_accounts.validator_list.pubkey(), - &wrong_reserve.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_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(), - &stake_pool_accounts.reserve_stake.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, - 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(), "{:?}", error); - - 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) - ); -}