Skip to content

Commit

Permalink
stake-pool: Prefund split account during redelegate (#5285)
Browse files Browse the repository at this point in the history
* stake-pool: Prefund split account during redelegate

* stake-pool-js: Add reserve account to redelegate

* Only pre-fund the rent-exempt reserve if needed

* Add error type for depleting the reserve during rebalance

* Do not deplete the reserve while funding rent-exemption

* Move error check for overdraw
  • Loading branch information
joncinque authored Sep 20, 2023
1 parent bcb81e7 commit 1b3a980
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 44 deletions.
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

0 comments on commit 1b3a980

Please sign in to comment.