Skip to content
This repository has been archived by the owner on Jan 10, 2025. It is now read-only.

stake-pool: Prefund split account during redelegate #5285

Merged
merged 6 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions stake-pool/js/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1092,6 +1092,7 @@ export async function redelegate(props: RedelegateProps) {
stakePool: stakePool.pubkey,
staker: stakePool.account.data.staker,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
stakePoolWithdrawAuthority,
ephemeralStake,
ephemeralStakeSeed,
Expand Down
3 changes: 3 additions & 0 deletions stake-pool/js/src/instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export type RedelegateParams = {
staker: PublicKey;
stakePoolWithdrawAuthority: PublicKey;
validatorList: PublicKey;
reserveStake: PublicKey;
sourceValidatorStake: PublicKey;
sourceTransientStake: PublicKey;
ephemeralStake: PublicKey;
Expand Down Expand Up @@ -841,6 +842,7 @@ export class StakePoolInstruction {
staker,
stakePoolWithdrawAuthority,
validatorList,
reserveStake,
sourceValidatorStake,
sourceTransientStake,
ephemeralStake,
Expand All @@ -858,6 +860,7 @@ export class StakePoolInstruction {
{ 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 },
Expand Down
5 changes: 5 additions & 0 deletions stake-pool/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ pub enum StakePoolError {
/// Provided mint does not have 9 decimals to match SOL
#[error("IncorrectMintDecimals")]
IncorrectMintDecimals,
/// Pool reserve does not have enough lamports to fund rent-exempt reserve in split
/// destination. Deposit more SOL in reserve, or pre-fund split destination with
/// the rent-exempt reserve for a stake account.
#[error("ReserveDepleted")]
ReserveDepleted,
}
impl From<StakePoolError> for ProgramError {
fn from(e: StakePoolError) -> Self {
Expand Down
44 changes: 24 additions & 20 deletions stake-pool/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,34 +487,36 @@ pub enum StakePoolInstruction {
/// The instruction only succeeds if the source transient stake account and
/// ephemeral stake account do not exist.
///
/// The amount of lamports to move must be at least twice rent-exemption
/// plus the minimum delegation amount. Rent-exemption is required for the
/// source transient stake account, and rent-exemption plus minimum delegation
/// The amount of lamports to move must be at least rent-exemption plus the
/// minimum delegation amount. Rent-exemption plus minimum delegation
/// is required for the destination ephemeral stake account.
///
/// The rent-exemption for the source transient account comes from the stake
/// pool reserve, if needed.
///
/// The amount that arrives at the destination validator in the end is
/// `redelegate_lamports - 2 * rent_exemption` if the destination transient
/// account does *not* exist, and `redelegate_lamports - rent_exemption` if
/// the destination transient account already exists. One `rent_exemption`
/// is deactivated with the source transient account during redelegation,
/// and another `rent_exemption` is deactivated when creating the destination
/// transient stake account.
/// `redelegate_lamports - rent_exemption` if the destination transient
/// account does *not* exist, and `redelegate_lamports` if the destination
/// transient account already exists. The `rent_exemption` is not activated
/// when creating the destination transient stake account, but if it already
/// exists, then the full amount is delegated.
///
/// 0. `[]` Stake pool
/// 1. `[s]` Stake pool staker
/// 2. `[]` Stake pool withdraw authority
/// 3. `[w]` Validator list
/// 4. `[w]` Source canonical stake account to split from
/// 5. `[w]` Source transient stake account to receive split and be redelegated
/// 6. `[w]` Uninitialized ephemeral stake account to receive redelegation
/// 7. `[w]` Destination transient stake account to receive ephemeral stake by merge
/// 8. `[]` Destination stake account to receive transient stake after activation
/// 9. `[]` Destination validator vote account
/// 10. `[]` Clock sysvar
/// 11. `[]` Stake History sysvar
/// 12. `[]` Stake Config sysvar
/// 13. `[]` System program
/// 14. `[]` Stake program
/// 4. `[w]` Reserve stake account, to withdraw rent exempt reserve
/// 5. `[w]` Source canonical stake account to split from
/// 6. `[w]` Source transient stake account to receive split and be redelegated
/// 7. `[w]` Uninitialized ephemeral stake account to receive redelegation
/// 8. `[w]` Destination transient stake account to receive ephemeral stake by merge
/// 9. `[]` Destination stake account to receive transient stake after activation
/// 10. `[]` Destination validator vote account
/// 11. `[]` Clock sysvar
/// 12. `[]` Stake History sysvar
/// 13. `[]` Stake Config sysvar
/// 14. `[]` System program
/// 15. `[]` Stake program
Redelegate {
/// Amount of lamports to redelegate
#[allow(dead_code)] // but it's not
Expand Down Expand Up @@ -920,6 +922,7 @@ pub fn redelegate(
staker: &Pubkey,
stake_pool_withdraw_authority: &Pubkey,
validator_list: &Pubkey,
reserve_stake: &Pubkey,
source_validator_stake: &Pubkey,
source_transient_stake: &Pubkey,
ephemeral_stake: &Pubkey,
Expand All @@ -936,6 +939,7 @@ pub fn redelegate(
AccountMeta::new_readonly(*staker, true),
AccountMeta::new_readonly(*stake_pool_withdraw_authority, false),
AccountMeta::new(*validator_list, false),
AccountMeta::new(*reserve_stake, false),
AccountMeta::new(*source_validator_stake, false),
AccountMeta::new(*source_transient_stake, false),
AccountMeta::new(*ephemeral_stake, false),
Expand Down
35 changes: 27 additions & 8 deletions stake-pool/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,7 @@ impl Processor {
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)?;
Expand Down Expand Up @@ -1829,6 +1830,7 @@ impl Processor {

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) =
Expand All @@ -1844,11 +1846,11 @@ impl Processor {
let current_minimum_delegation = minimum_delegation(stake_minimum_delegation);

// check that we're redelegating enough
let destination_transient_lamports = {
{
// 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.saturating_mul(2));
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",
Expand Down Expand Up @@ -1877,10 +1879,7 @@ impl Processor {
);
return Err(ProgramError::InsufficientFunds);
}
lamports
.checked_sub(stake_rent)
.ok_or(StakePoolError::CalculationFailure)?
};
}

// check source account state
let (_, stake) = get_stake_state(source_validator_stake_account_info)?;
Expand Down Expand Up @@ -1945,6 +1944,25 @@ impl Processor {
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(),
Expand Down Expand Up @@ -2021,7 +2039,7 @@ impl Processor {
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(destination_transient_lamports)
.checked_add(lamports)
.ok_or(StakePoolError::CalculationFailure)?
.into();

Expand Down Expand Up @@ -2088,7 +2106,7 @@ impl Processor {
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
destination_transient_lamports,
lamports,
destination_transient_stake_account_info.clone(),
)?;
validator_stake_info.transient_seed_suffix =
Expand Down Expand Up @@ -4019,6 +4037,7 @@ impl PrintProgramError for StakePoolError {
StakePoolError::UnsupportedFeeAccountExtension => msg!("Error: fee account has an unsupported extension"),
StakePoolError::ExceededSlippage => msg!("Error: instruction exceeds desired slippage limit"),
StakePoolError::IncorrectMintDecimals => msg!("Error: Provided mint does not have 9 decimals to match SOL"),
StakePoolError::ReserveDepleted => msg!("Error: Pool reserve does not have enough lamports to fund rent-exempt reserve in split destination. Deposit more SOL in reserve, or pre-fund split destination with the rent-exempt reserve for a stake account."),
}
}
}
1 change: 1 addition & 0 deletions stake-pool/program/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,7 @@ impl StakePoolAccounts {
&self.staker.pubkey(),
&self.withdraw_authority,
&self.validator_list.pubkey(),
&self.reserve_stake.pubkey(),
source_validator_stake,
source_transient_stake,
ephemeral_stake,
Expand Down
Loading