Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 1b3a980

Browse files
authored
stake-pool: Prefund split account during redelegate (#5285)
* 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
1 parent bcb81e7 commit 1b3a980

File tree

7 files changed

+178
-44
lines changed

7 files changed

+178
-44
lines changed

stake-pool/js/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,7 @@ export async function redelegate(props: RedelegateProps) {
10921092
stakePool: stakePool.pubkey,
10931093
staker: stakePool.account.data.staker,
10941094
validatorList: stakePool.account.data.validatorList,
1095+
reserveStake: stakePool.account.data.reserveStake,
10951096
stakePoolWithdrawAuthority,
10961097
ephemeralStake,
10971098
ephemeralStakeSeed,

stake-pool/js/src/instructions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ export type RedelegateParams = {
325325
staker: PublicKey;
326326
stakePoolWithdrawAuthority: PublicKey;
327327
validatorList: PublicKey;
328+
reserveStake: PublicKey;
328329
sourceValidatorStake: PublicKey;
329330
sourceTransientStake: PublicKey;
330331
ephemeralStake: PublicKey;
@@ -841,6 +842,7 @@ export class StakePoolInstruction {
841842
staker,
842843
stakePoolWithdrawAuthority,
843844
validatorList,
845+
reserveStake,
844846
sourceValidatorStake,
845847
sourceTransientStake,
846848
ephemeralStake,
@@ -858,6 +860,7 @@ export class StakePoolInstruction {
858860
{ pubkey: staker, isSigner: true, isWritable: false },
859861
{ pubkey: stakePoolWithdrawAuthority, isSigner: false, isWritable: false },
860862
{ pubkey: validatorList, isSigner: false, isWritable: true },
863+
{ pubkey: reserveStake, isSigner: false, isWritable: true },
861864
{ pubkey: sourceValidatorStake, isSigner: false, isWritable: true },
862865
{ pubkey: sourceTransientStake, isSigner: false, isWritable: true },
863866
{ pubkey: ephemeralStake, isSigner: false, isWritable: true },

stake-pool/program/src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ pub enum StakePoolError {
149149
/// Provided mint does not have 9 decimals to match SOL
150150
#[error("IncorrectMintDecimals")]
151151
IncorrectMintDecimals,
152+
/// Pool reserve does not have enough lamports to fund rent-exempt reserve in split
153+
/// destination. Deposit more SOL in reserve, or pre-fund split destination with
154+
/// the rent-exempt reserve for a stake account.
155+
#[error("ReserveDepleted")]
156+
ReserveDepleted,
152157
}
153158
impl From<StakePoolError> for ProgramError {
154159
fn from(e: StakePoolError) -> Self {

stake-pool/program/src/instruction.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -487,34 +487,36 @@ pub enum StakePoolInstruction {
487487
/// The instruction only succeeds if the source transient stake account and
488488
/// ephemeral stake account do not exist.
489489
///
490-
/// The amount of lamports to move must be at least twice rent-exemption
491-
/// plus the minimum delegation amount. Rent-exemption is required for the
492-
/// source transient stake account, and rent-exemption plus minimum delegation
490+
/// The amount of lamports to move must be at least rent-exemption plus the
491+
/// minimum delegation amount. Rent-exemption plus minimum delegation
493492
/// is required for the destination ephemeral stake account.
494493
///
494+
/// The rent-exemption for the source transient account comes from the stake
495+
/// pool reserve, if needed.
496+
///
495497
/// The amount that arrives at the destination validator in the end is
496-
/// `redelegate_lamports - 2 * rent_exemption` if the destination transient
497-
/// account does *not* exist, and `redelegate_lamports - rent_exemption` if
498-
/// the destination transient account already exists. One `rent_exemption`
499-
/// is deactivated with the source transient account during redelegation,
500-
/// and another `rent_exemption` is deactivated when creating the destination
501-
/// transient stake account.
498+
/// `redelegate_lamports - rent_exemption` if the destination transient
499+
/// account does *not* exist, and `redelegate_lamports` if the destination
500+
/// transient account already exists. The `rent_exemption` is not activated
501+
/// when creating the destination transient stake account, but if it already
502+
/// exists, then the full amount is delegated.
502503
///
503504
/// 0. `[]` Stake pool
504505
/// 1. `[s]` Stake pool staker
505506
/// 2. `[]` Stake pool withdraw authority
506507
/// 3. `[w]` Validator list
507-
/// 4. `[w]` Source canonical stake account to split from
508-
/// 5. `[w]` Source transient stake account to receive split and be redelegated
509-
/// 6. `[w]` Uninitialized ephemeral stake account to receive redelegation
510-
/// 7. `[w]` Destination transient stake account to receive ephemeral stake by merge
511-
/// 8. `[]` Destination stake account to receive transient stake after activation
512-
/// 9. `[]` Destination validator vote account
513-
/// 10. `[]` Clock sysvar
514-
/// 11. `[]` Stake History sysvar
515-
/// 12. `[]` Stake Config sysvar
516-
/// 13. `[]` System program
517-
/// 14. `[]` Stake program
508+
/// 4. `[w]` Reserve stake account, to withdraw rent exempt reserve
509+
/// 5. `[w]` Source canonical stake account to split from
510+
/// 6. `[w]` Source transient stake account to receive split and be redelegated
511+
/// 7. `[w]` Uninitialized ephemeral stake account to receive redelegation
512+
/// 8. `[w]` Destination transient stake account to receive ephemeral stake by merge
513+
/// 9. `[]` Destination stake account to receive transient stake after activation
514+
/// 10. `[]` Destination validator vote account
515+
/// 11. `[]` Clock sysvar
516+
/// 12. `[]` Stake History sysvar
517+
/// 13. `[]` Stake Config sysvar
518+
/// 14. `[]` System program
519+
/// 15. `[]` Stake program
518520
Redelegate {
519521
/// Amount of lamports to redelegate
520522
#[allow(dead_code)] // but it's not
@@ -920,6 +922,7 @@ pub fn redelegate(
920922
staker: &Pubkey,
921923
stake_pool_withdraw_authority: &Pubkey,
922924
validator_list: &Pubkey,
925+
reserve_stake: &Pubkey,
923926
source_validator_stake: &Pubkey,
924927
source_transient_stake: &Pubkey,
925928
ephemeral_stake: &Pubkey,
@@ -936,6 +939,7 @@ pub fn redelegate(
936939
AccountMeta::new_readonly(*staker, true),
937940
AccountMeta::new_readonly(*stake_pool_withdraw_authority, false),
938941
AccountMeta::new(*validator_list, false),
942+
AccountMeta::new(*reserve_stake, false),
939943
AccountMeta::new(*source_validator_stake, false),
940944
AccountMeta::new(*source_transient_stake, false),
941945
AccountMeta::new(*ephemeral_stake, false),

stake-pool/program/src/processor.rs

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1793,6 +1793,7 @@ impl Processor {
17931793
let staker_info = next_account_info(account_info_iter)?;
17941794
let withdraw_authority_info = next_account_info(account_info_iter)?;
17951795
let validator_list_info = next_account_info(account_info_iter)?;
1796+
let reserve_stake_info = next_account_info(account_info_iter)?;
17961797
let source_validator_stake_account_info = next_account_info(account_info_iter)?;
17971798
let source_transient_stake_account_info = next_account_info(account_info_iter)?;
17981799
let ephemeral_stake_account_info = next_account_info(account_info_iter)?;
@@ -1829,6 +1830,7 @@ impl Processor {
18291830

18301831
stake_pool.check_validator_list(validator_list_info)?;
18311832
check_account_owner(validator_list_info, program_id)?;
1833+
stake_pool.check_reserve_stake(reserve_stake_info)?;
18321834

18331835
let mut validator_list_data = validator_list_info.data.borrow_mut();
18341836
let (header, mut validator_list) =
@@ -1844,11 +1846,11 @@ impl Processor {
18441846
let current_minimum_delegation = minimum_delegation(stake_minimum_delegation);
18451847

18461848
// check that we're redelegating enough
1847-
let destination_transient_lamports = {
1849+
{
18481850
// redelegation requires that the source account maintains rent exemption and that
18491851
// the destination account has rent-exemption and minimum delegation
18501852
let minimum_redelegation_lamports =
1851-
current_minimum_delegation.saturating_add(stake_rent.saturating_mul(2));
1853+
current_minimum_delegation.saturating_add(stake_rent);
18521854
if lamports < minimum_redelegation_lamports {
18531855
msg!(
18541856
"Need more than {} lamports for redelegated stake and transient stake to meet minimum delegation requirement, {} provided",
@@ -1877,10 +1879,7 @@ impl Processor {
18771879
);
18781880
return Err(ProgramError::InsufficientFunds);
18791881
}
1880-
lamports
1881-
.checked_sub(stake_rent)
1882-
.ok_or(StakePoolError::CalculationFailure)?
1883-
};
1882+
}
18841883

18851884
// check source account state
18861885
let (_, stake) = get_stake_state(source_validator_stake_account_info)?;
@@ -1945,6 +1944,25 @@ impl Processor {
19451944
stake_space,
19461945
)?;
19471946

1947+
// if needed, pre-fund the rent-exempt reserve from the reserve stake
1948+
let required_lamports_for_rent_exemption =
1949+
stake_rent.saturating_sub(source_transient_stake_account_info.lamports());
1950+
if required_lamports_for_rent_exemption > 0 {
1951+
if required_lamports_for_rent_exemption >= reserve_stake_info.lamports() {
1952+
return Err(StakePoolError::ReserveDepleted.into());
1953+
}
1954+
Self::stake_withdraw(
1955+
stake_pool_info.key,
1956+
reserve_stake_info.clone(),
1957+
withdraw_authority_info.clone(),
1958+
AUTHORITY_WITHDRAW,
1959+
stake_pool.stake_withdraw_bump_seed,
1960+
source_transient_stake_account_info.clone(),
1961+
clock_info.clone(),
1962+
stake_history_info.clone(),
1963+
required_lamports_for_rent_exemption,
1964+
)?;
1965+
}
19481966
Self::stake_split(
19491967
stake_pool_info.key,
19501968
source_validator_stake_account_info.clone(),
@@ -2021,7 +2039,7 @@ impl Processor {
20212039
u64::from(validator_stake_info.transient_stake_lamports) > 0;
20222040
validator_stake_info.transient_stake_lamports =
20232041
u64::from(validator_stake_info.transient_stake_lamports)
2024-
.checked_add(destination_transient_lamports)
2042+
.checked_add(lamports)
20252043
.ok_or(StakePoolError::CalculationFailure)?
20262044
.into();
20272045

@@ -2088,7 +2106,7 @@ impl Processor {
20882106
withdraw_authority_info.clone(),
20892107
AUTHORITY_WITHDRAW,
20902108
stake_pool.stake_withdraw_bump_seed,
2091-
destination_transient_lamports,
2109+
lamports,
20922110
destination_transient_stake_account_info.clone(),
20932111
)?;
20942112
validator_stake_info.transient_seed_suffix =
@@ -4019,6 +4037,7 @@ impl PrintProgramError for StakePoolError {
40194037
StakePoolError::UnsupportedFeeAccountExtension => msg!("Error: fee account has an unsupported extension"),
40204038
StakePoolError::ExceededSlippage => msg!("Error: instruction exceeds desired slippage limit"),
40214039
StakePoolError::IncorrectMintDecimals => msg!("Error: Provided mint does not have 9 decimals to match SOL"),
4040+
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."),
40224041
}
40234042
}
40244043
}

stake-pool/program/tests/helpers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,6 +1890,7 @@ impl StakePoolAccounts {
18901890
&self.staker.pubkey(),
18911891
&self.withdraw_authority,
18921892
&self.validator_list.pubkey(),
1893+
&self.reserve_stake.pubkey(),
18931894
source_validator_stake,
18941895
source_transient_stake,
18951896
ephemeral_stake,

0 commit comments

Comments
 (0)