Skip to content

Commit

Permalink
stake-pool: Fund rent-exemption from reserve in decrease_additional (
Browse files Browse the repository at this point in the history
…#5288)

* stake-pool: Fund rent-exemption from reserve in `decrease_additional`

* stake-pool-js: Add reserve stake to "decrease additional"

* Only withdraw required lamports into split destination

* Avoid depleting the reserve

* Use actual lamport number from ephemeral account

* Cargo fmt

* Move error check
  • Loading branch information
joncinque authored Sep 20, 2023
1 parent 1b3a980 commit 7492e38
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 36 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 @@ -795,6 +795,7 @@ export async function decreaseValidatorStake(
stakePool: stakePoolAddress,
staker: stakePool.account.data.staker,
validatorList: stakePool.account.data.validatorList,
reserveStake: stakePool.account.data.reserveStake,
transientStakeSeed: transientStakeSeed.toNumber(),
withdrawAuthority,
validatorStake,
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 @@ -226,6 +226,7 @@ export type DecreaseValidatorStakeParams = {
};

export interface DecreaseAdditionalValidatorStakeParams extends DecreaseValidatorStakeParams {
reserveStake: PublicKey;
ephemeralStake: PublicKey;
ephemeralStakeSeed: number;
}
Expand Down Expand Up @@ -612,6 +613,7 @@ export class StakePoolInstruction {
staker,
withdrawAuthority,
validatorList,
reserveStake,
validatorStake,
transientStake,
lamports,
Expand All @@ -628,6 +630,7 @@ export class StakePoolInstruction {
{ pubkey: staker, isSigner: true, isWritable: false },
{ pubkey: withdrawAuthority, isSigner: false, isWritable: false },
{ pubkey: validatorList, isSigner: false, isWritable: true },
{ pubkey: reserveStake, isSigner: false, isWritable: true },
{ pubkey: validatorStake, isSigner: false, isWritable: true },
{ pubkey: ephemeralStake, isSigner: false, isWritable: true },
{ pubkey: transientStake, isSigner: false, isWritable: true },
Expand Down
3 changes: 3 additions & 0 deletions stake-pool/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ pub enum StakePoolError {
/// the rent-exempt reserve for a stake account.
#[error("ReserveDepleted")]
ReserveDepleted,
/// Missing required sysvar account
#[error("Missing required sysvar account")]
MissingRequiredSysvar,
}
impl From<StakePoolError> for ProgramError {
fn from(e: StakePoolError) -> Self {
Expand Down
29 changes: 18 additions & 11 deletions stake-pool/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,24 +446,28 @@ pub enum StakePoolInstruction {
///
/// Works regardless if the transient stake account already exists.
///
/// Internally, this instruction splits a validator stake account into an
/// ephemeral stake account, deactivates it, then merges or splits it into
/// the transient stake account delegated to the appropriate validator.
/// Internally, this instruction:
/// * withdraws rent-exempt reserve lamports from the reserve into the ephemeral stake
/// * splits a validator stake account into an ephemeral stake account
/// * deactivates the ephemeral account
/// * merges or splits the ephemeral account into the transient stake account
/// delegated to the appropriate validator
///
/// The amount of lamports to move must be at least rent-exemption plus
/// The amount of lamports to move must be at least
/// `max(crate::MINIMUM_ACTIVE_STAKE, solana_program::stake::tools::get_minimum_delegation())`.
///
/// 0. `[]` Stake pool
/// 1. `[s]` Stake pool staker
/// 2. `[]` Stake pool withdraw authority
/// 3. `[w]` Validator list
/// 4. `[w]` Canonical stake account to split from
/// 5. `[w]` Uninitialized ephemeral stake account to receive stake
/// 6. `[w]` Transient stake account
/// 7. `[]` Clock sysvar
/// 8. '[]' Stake history sysvar
/// 9. `[]` System program
/// 10. `[]` Stake program
/// 4. `[w]` Reserve stake account, to fund rent exempt reserve
/// 5. `[w]` Canonical stake account to split from
/// 6. `[w]` Uninitialized ephemeral stake account to receive stake
/// 7. `[w]` Transient stake account
/// 8. `[]` Clock sysvar
/// 9. '[]' Stake history sysvar
/// 10. `[]` System program
/// 11. `[]` Stake program
DecreaseAdditionalValidatorStake {
/// amount of lamports to split into the transient stake account
lamports: u64,
Expand Down Expand Up @@ -793,6 +797,7 @@ pub fn decrease_additional_validator_stake(
staker: &Pubkey,
stake_pool_withdraw_authority: &Pubkey,
validator_list: &Pubkey,
reserve_stake: &Pubkey,
validator_stake: &Pubkey,
ephemeral_stake: &Pubkey,
transient_stake: &Pubkey,
Expand All @@ -805,6 +810,7 @@ pub fn decrease_additional_validator_stake(
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(*validator_stake, false),
AccountMeta::new(*ephemeral_stake, false),
AccountMeta::new(*transient_stake, false),
Expand Down Expand Up @@ -1211,6 +1217,7 @@ pub fn decrease_additional_validator_stake_with_vote(
&stake_pool.staker,
&pool_withdraw_authority,
&stake_pool.validator_list,
&stake_pool.reserve_stake,
&validator_stake_address,
&ephemeral_stake_address,
&transient_stake_address,
Expand Down
56 changes: 48 additions & 8 deletions stake-pool/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1241,12 +1241,16 @@ impl Processor {
lamports: u64,
transient_stake_seed: u64,
maybe_ephemeral_stake_seed: Option<u64>,
fund_rent_exempt_reserve: bool,
) -> 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 maybe_reserve_stake_info = fund_rent_exempt_reserve
.then(|| next_account_info(account_info_iter))
.transpose()?;
let validator_stake_account_info = next_account_info(account_info_iter)?;
let maybe_ephemeral_stake_account_info = maybe_ephemeral_stake_seed
.map(|_| next_account_info(account_info_iter))
Expand Down Expand Up @@ -1298,6 +1302,10 @@ impl Processor {
return Err(StakePoolError::InvalidState.into());
}

if let Some(reserve_stake_info) = maybe_reserve_stake_info {
stake_pool.check_reserve_stake(reserve_stake_info)?;
}

let (meta, stake) = get_stake_state(validator_stake_account_info)?;
let vote_account_address = stake.delegation.voter_pubkey;

Expand Down Expand Up @@ -1339,10 +1347,10 @@ impl Processor {
}

let stake_space = std::mem::size_of::<stake::state::StakeState>();
let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
let stake_rent = rent.minimum_balance(stake_space);
let current_minimum_lamports =
stake_rent.saturating_add(minimum_delegation(stake_minimum_delegation));

let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
let current_minimum_lamports = minimum_delegation(stake_minimum_delegation);
if lamports < current_minimum_lamports {
msg!(
"Need at least {} lamports for transient stake to meet minimum delegation and rent-exempt requirements, {} provided",
Expand All @@ -1366,7 +1374,7 @@ impl Processor {
return Err(ProgramError::InsufficientFunds);
}

let source_stake_account_info =
let (source_stake_account_info, split_lamports) =
if let Some((ephemeral_stake_seed, ephemeral_stake_account_info)) =
maybe_ephemeral_stake_seed.zip(maybe_ephemeral_stake_account_info)
{
Expand All @@ -1388,6 +1396,30 @@ impl Processor {
stake_space,
)?;

// if needed, withdraw rent-exempt reserve for ephemeral account
if let Some(reserve_stake_info) = maybe_reserve_stake_info {
let required_lamports_for_rent_exemption =
stake_rent.saturating_sub(ephemeral_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());
}
let stake_history_info = maybe_stake_history_info
.ok_or(StakePoolError::MissingRequiredSysvar)?;
Self::stake_withdraw(
stake_pool_info.key,
reserve_stake_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
ephemeral_stake_account_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
required_lamports_for_rent_exemption,
)?;
}
}

// split into ephemeral stake account
Self::stake_split(
stake_pool_info.key,
Expand All @@ -1408,11 +1440,14 @@ impl Processor {
stake_pool.stake_withdraw_bump_seed,
)?;

ephemeral_stake_account_info
(
ephemeral_stake_account_info,
ephemeral_stake_account_info.lamports(),
)
} else {
// if no ephemeral account is provided, split everything from the
// validator stake account, into the transient stake account
validator_stake_account_info
(validator_stake_account_info, lamports)
};

let transient_stake_bump_seed = check_transient_stake_address(
Expand Down Expand Up @@ -1459,7 +1494,7 @@ impl Processor {
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
lamports,
split_lamports,
transient_stake_account_info.clone(),
)?;

Expand All @@ -1482,9 +1517,11 @@ impl Processor {
.checked_sub(lamports)
.ok_or(StakePoolError::CalculationFailure)?
.into();
// `split_lamports` may be greater than `lamports` if the reserve stake
// funded the rent-exempt reserve
validator_stake_info.transient_stake_lamports =
u64::from(validator_stake_info.transient_stake_lamports)
.checked_add(lamports)
.checked_add(split_lamports)
.ok_or(StakePoolError::CalculationFailure)?
.into();
validator_stake_info.transient_seed_suffix = transient_stake_seed.into();
Expand Down Expand Up @@ -3811,6 +3848,7 @@ impl Processor {
lamports,
transient_stake_seed,
None,
false,
)
}
StakePoolInstruction::DecreaseAdditionalValidatorStake {
Expand All @@ -3825,6 +3863,7 @@ impl Processor {
lamports,
transient_stake_seed,
Some(ephemeral_stake_seed),
true,
)
}
StakePoolInstruction::IncreaseValidatorStake {
Expand Down Expand Up @@ -4038,6 +4077,7 @@ impl PrintProgramError for StakePoolError {
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."),
StakePoolError::MissingRequiredSysvar => msg!("Missing required sysvar account"),
}
}
}
Loading

0 comments on commit 7492e38

Please sign in to comment.