Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

stake-pool: Fund rent-exemption from reserve in decrease_additional #5288

Merged
merged 7 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 @@ -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
Loading