Skip to content

Commit

Permalink
stake-pool: Add DecreaseAdditionalValidatorStake instruction (solan…
Browse files Browse the repository at this point in the history
…a-labs#3925)

* stake-pool: Add `DecreaseAdditionalValidatorStake` instruction

* Update checks for deduping
  • Loading branch information
joncinque authored and HaoranYi committed Jul 19, 2023
1 parent 94ffbb6 commit c0a780c
Show file tree
Hide file tree
Showing 4 changed files with 654 additions and 198 deletions.
115 changes: 115 additions & 0 deletions stake-pool/program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,37 @@ pub enum StakePoolInstruction {
/// seed used to create ephemeral account.
ephemeral_stake_seed: u64,
},

/// (Staker only) Decrease active stake again from a validator, eventually moving it to the reserve
///
/// 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.
///
/// The amount of lamports to move must be at least rent-exemption plus
/// `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
DecreaseAdditionalValidatorStake {
/// amount of lamports to split into the transient stake account
lamports: u64,
/// seed used to create transient stake account
transient_stake_seed: u64,
/// seed used to create ephemeral account.
ephemeral_stake_seed: u64,
},
}

/// Creates an 'initialize' instruction.
Expand Down Expand Up @@ -595,6 +626,47 @@ pub fn decrease_validator_stake(
}
}

/// Creates `DecreaseAdditionalValidatorStake` instruction (rebalance from
/// validator account to transient account)
pub fn decrease_additional_validator_stake(
program_id: &Pubkey,
stake_pool: &Pubkey,
staker: &Pubkey,
stake_pool_withdraw_authority: &Pubkey,
validator_list: &Pubkey,
validator_stake: &Pubkey,
ephemeral_stake: &Pubkey,
transient_stake: &Pubkey,
lamports: u64,
transient_stake_seed: u64,
ephemeral_stake_seed: u64,
) -> Instruction {
let accounts = vec![
AccountMeta::new_readonly(*stake_pool, false),
AccountMeta::new_readonly(*staker, true),
AccountMeta::new_readonly(*stake_pool_withdraw_authority, false),
AccountMeta::new(*validator_list, false),
AccountMeta::new(*validator_stake, false),
AccountMeta::new(*ephemeral_stake, false),
AccountMeta::new(*transient_stake, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
AccountMeta::new_readonly(system_program::id(), false),
AccountMeta::new_readonly(stake::program::id(), false),
];
Instruction {
program_id: *program_id,
accounts,
data: StakePoolInstruction::DecreaseAdditionalValidatorStake {
lamports,
transient_stake_seed,
ephemeral_stake_seed,
}
.try_to_vec()
.unwrap(),
}
}

/// Creates `IncreaseValidatorStake` instruction (rebalance from reserve account to
/// transient account)
pub fn increase_validator_stake(
Expand Down Expand Up @@ -895,6 +967,49 @@ pub fn decrease_validator_stake_with_vote(
)
}

/// Create a `DecreaseAdditionalValidatorStake` instruction given an existing
/// stake pool and vote account
pub fn decrease_additional_validator_stake_with_vote(
program_id: &Pubkey,
stake_pool: &StakePool,
stake_pool_address: &Pubkey,
vote_account_address: &Pubkey,
lamports: u64,
validator_stake_seed: Option<NonZeroU32>,
transient_stake_seed: u64,
ephemeral_stake_seed: u64,
) -> Instruction {
let pool_withdraw_authority =
find_withdraw_authority_program_address(program_id, stake_pool_address).0;
let (validator_stake_address, _) = find_stake_program_address(
program_id,
vote_account_address,
stake_pool_address,
validator_stake_seed,
);
let (ephemeral_stake_address, _) =
find_ephemeral_stake_program_address(program_id, stake_pool_address, ephemeral_stake_seed);
let (transient_stake_address, _) = find_transient_stake_program_address(
program_id,
vote_account_address,
stake_pool_address,
transient_stake_seed,
);
decrease_additional_validator_stake(
program_id,
stake_pool_address,
&stake_pool.staker,
&pool_withdraw_authority,
&stake_pool.validator_list,
&validator_stake_address,
&ephemeral_stake_address,
&transient_stake_address,
lamports,
transient_stake_seed,
ephemeral_stake_seed,
)
}

/// Creates `UpdateValidatorListBalance` instruction (update validator stake account balances)
pub fn update_validator_list_balance(
program_id: &Pubkey,
Expand Down
195 changes: 154 additions & 41 deletions stake-pool/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,18 +1124,31 @@ impl Processor {
accounts: &[AccountInfo],
lamports: u64,
transient_stake_seed: u64,
maybe_ephemeral_stake_seed: Option<u64>,
) -> 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 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))
.transpose()?;
let transient_stake_account_info = next_account_info(account_info_iter)?;
let clock_info = next_account_info(account_info_iter)?;
let clock = &Clock::from_account_info(clock_info)?;
let rent_info = next_account_info(account_info_iter)?;
let rent = &Rent::from_account_info(rent_info)?;
let rent = if maybe_ephemeral_stake_seed.is_some() {
// instruction with ephemeral account doesn't take the rent account
Rent::get()?
} else {
// legacy instruction takes the rent account
let rent_info = next_account_info(account_info_iter)?;
Rent::from_account_info(rent_info)?
};
let maybe_stake_history_info = maybe_ephemeral_stake_seed
.map(|_| next_account_info(account_info_iter))
.transpose()?;
let system_program_info = next_account_info(account_info_iter)?;
let stake_program_info = next_account_info(account_info_iter)?;

Expand Down Expand Up @@ -1191,24 +1204,21 @@ impl Processor {
NonZeroU32::new(validator_stake_info.validator_seed_suffix),
)?;
if validator_stake_info.transient_stake_lamports > 0 {
return Err(StakePoolError::TransientAccountInUse.into());
if maybe_ephemeral_stake_seed.is_none() {
msg!("Attempting to decrease stake on a validator with pending transient stake, use DecreaseAdditionalValidatorStake with the existing seed");
return Err(StakePoolError::TransientAccountInUse.into());
}
if transient_stake_seed != validator_stake_info.transient_seed_suffix {
msg!(
"Transient stake already exists with seed {}, you must use that one",
validator_stake_info.transient_seed_suffix
);
return Err(ProgramError::InvalidSeeds);
}
// Let the runtime check to see if the merge is valid, so there's no
// explicit check here that the transient stake is decreasing
}

let transient_stake_bump_seed = check_transient_stake_address(
program_id,
stake_pool_info.key,
transient_stake_account_info.key,
&vote_account_address,
transient_stake_seed,
)?;
let transient_stake_account_signer_seeds: &[&[_]] = &[
TRANSIENT_STAKE_SEED_PREFIX,
&vote_account_address.to_bytes(),
&stake_pool_info.key.to_bytes(),
&transient_stake_seed.to_le_bytes(),
&[transient_stake_bump_seed],
];

let stake_minimum_delegation = stake::tools::get_minimum_delegation()?;
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake::state::StakeState>());
let current_minimum_lamports =
Expand Down Expand Up @@ -1236,38 +1246,126 @@ impl Processor {
return Err(ProgramError::InsufficientFunds);
}

create_stake_account(
transient_stake_account_info.clone(),
transient_stake_account_signer_seeds,
system_program_info.clone(),
)?;
let source_stake_account_info =
if let Some((ephemeral_stake_seed, ephemeral_stake_account_info)) =
maybe_ephemeral_stake_seed.zip(maybe_ephemeral_stake_account_info)
{
let ephemeral_stake_bump_seed = check_ephemeral_stake_address(
program_id,
stake_pool_info.key,
ephemeral_stake_account_info.key,
ephemeral_stake_seed,
)?;
let ephemeral_stake_account_signer_seeds: &[&[_]] = &[
EPHEMERAL_STAKE_SEED_PREFIX,
&stake_pool_info.key.to_bytes(),
&ephemeral_stake_seed.to_le_bytes(),
&[ephemeral_stake_bump_seed],
];
create_stake_account(
ephemeral_stake_account_info.clone(),
ephemeral_stake_account_signer_seeds,
system_program_info.clone(),
)?;

// split into transient stake account
Self::stake_split(
stake_pool_info.key,
validator_stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
lamports,
transient_stake_account_info.clone(),
)?;
// split into ephemeral stake account
Self::stake_split(
stake_pool_info.key,
validator_stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
lamports,
ephemeral_stake_account_info.clone(),
)?;

// deactivate transient stake
Self::stake_deactivate(
transient_stake_account_info.clone(),
clock_info.clone(),
withdraw_authority_info.clone(),
Self::stake_deactivate(
ephemeral_stake_account_info.clone(),
clock_info.clone(),
withdraw_authority_info.clone(),
stake_pool_info.key,
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
)?;

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

let transient_stake_bump_seed = check_transient_stake_address(
program_id,
stake_pool_info.key,
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
transient_stake_account_info.key,
&vote_account_address,
transient_stake_seed,
)?;

if validator_stake_info.transient_stake_lamports > 0 {
let stake_history_info = maybe_stake_history_info.unwrap();
// transient stake exists, try to merge from the source account,
// which is always an ephemeral account
Self::stake_merge(
stake_pool_info.key,
source_stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
transient_stake_account_info.clone(),
clock_info.clone(),
stake_history_info.clone(),
stake_program_info.clone(),
)?;
} else {
let transient_stake_account_signer_seeds: &[&[_]] = &[
TRANSIENT_STAKE_SEED_PREFIX,
&vote_account_address.to_bytes(),
&stake_pool_info.key.to_bytes(),
&transient_stake_seed.to_le_bytes(),
&[transient_stake_bump_seed],
];

create_stake_account(
transient_stake_account_info.clone(),
transient_stake_account_signer_seeds,
system_program_info.clone(),
)?;

// split into transient stake account
Self::stake_split(
stake_pool_info.key,
source_stake_account_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
lamports,
transient_stake_account_info.clone(),
)?;

// Deactivate transient stake if necessary
let (_, stake) = get_stake_state(transient_stake_account_info)?;
if stake.delegation.deactivation_epoch == Epoch::MAX {
Self::stake_deactivate(
transient_stake_account_info.clone(),
clock_info.clone(),
withdraw_authority_info.clone(),
stake_pool_info.key,
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
)?;
}
}

validator_stake_info.active_stake_lamports = validator_stake_info
.active_stake_lamports
.checked_sub(lamports)
.ok_or(StakePoolError::CalculationFailure)?;
validator_stake_info.transient_stake_lamports = lamports;
validator_stake_info.transient_stake_lamports = validator_stake_info
.transient_stake_lamports
.checked_add(lamports)
.ok_or(StakePoolError::CalculationFailure)?;
validator_stake_info.transient_seed_suffix = transient_stake_seed;

Ok(())
Expand Down Expand Up @@ -3248,6 +3346,21 @@ impl Processor {
accounts,
lamports,
transient_stake_seed,
None,
)
}
StakePoolInstruction::DecreaseAdditionalValidatorStake {
lamports,
transient_stake_seed,
ephemeral_stake_seed,
} => {
msg!("Instruction: DecreaseAdditionalValidatorStake");
Self::process_decrease_validator_stake(
program_id,
accounts,
lamports,
transient_stake_seed,
Some(ephemeral_stake_seed),
)
}
StakePoolInstruction::IncreaseValidatorStake {
Expand Down
Loading

0 comments on commit c0a780c

Please sign in to comment.