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

Commit 6c9ca0d

Browse files
authored
stake-pool: Use delegation.stake for accounting (#1934)
* stake-pool: Use `delegation.stake` for accounting * Clippy * Improve test and remove spl-math dependency * Address smaller review comments * Fix calculation, improve tests * Clippy * Remove esm from mocha * Revert "Remove esm from mocha" This reverts commit c0f25ab.
1 parent 4c5f4df commit 6c9ca0d

File tree

12 files changed

+390
-194
lines changed

12 files changed

+390
-194
lines changed

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

stake-pool/cli/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ fn command_deposit(
597597
&stake,
598598
&config.staker.pubkey(),
599599
&validator_stake_account,
600+
&stake_pool.reserve_stake,
600601
&token_receiver,
601602
&stake_pool.pool_mint,
602603
&spl_token::id(),
@@ -610,6 +611,7 @@ fn command_deposit(
610611
&stake,
611612
&config.staker.pubkey(),
612613
&validator_stake_account,
614+
&stake_pool.reserve_stake,
613615
&token_receiver,
614616
&stake_pool.pool_mint,
615617
&spl_token::id(),

stake-pool/program/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ num_enum = "0.5.1"
2020
serde = "1.0.121"
2121
serde_derive = "1.0.103"
2222
solana-program = "1.6.11"
23-
spl-math = { version = "0.1", path = "../../libraries/math", features = [ "no-entrypoint" ] }
2423
spl-token = { version = "3.1", path = "../../token/program", features = [ "no-entrypoint" ] }
2524
thiserror = "1.0"
2625
bincode = "1.3.1"

stake-pool/program/src/instruction.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,10 @@ pub enum StakePoolInstruction {
236236
/// 3. `[]` Stake pool withdraw authority
237237
/// 4. `[w]` Stake account to join the pool (withdraw authority for the stake account should be first set to the stake pool deposit authority)
238238
/// 5. `[w]` Validator stake account for the stake account to be merged with
239-
/// 6. `[w]` User account to receive pool tokens
239+
/// 6. `[w]` Reserve stake account, to withdraw rent exempt reserve
240+
/// 7. `[w]` User account to receive pool tokens
240241
/// 8. `[w]` Pool token mint account
241-
/// 9. '[]' Sysvar clock account (required)
242+
/// 9. '[]' Sysvar clock account
242243
/// 10. '[]' Sysvar stake history account
243244
/// 11. `[]` Pool token program id,
244245
/// 12. `[]` Stake program id,
@@ -781,6 +782,7 @@ pub fn deposit(
781782
deposit_stake_address: &Pubkey,
782783
deposit_stake_withdraw_authority: &Pubkey,
783784
validator_stake_account: &Pubkey,
785+
reserve_stake_account: &Pubkey,
784786
pool_tokens_to: &Pubkey,
785787
pool_mint: &Pubkey,
786788
token_program_id: &Pubkey,
@@ -794,6 +796,7 @@ pub fn deposit(
794796
AccountMeta::new_readonly(*stake_pool_withdraw_authority, false),
795797
AccountMeta::new(*deposit_stake_address, false),
796798
AccountMeta::new(*validator_stake_account, false),
799+
AccountMeta::new(*reserve_stake_account, false),
797800
AccountMeta::new(*pool_tokens_to, false),
798801
AccountMeta::new(*pool_mint, false),
799802
AccountMeta::new_readonly(sysvar::clock::id(), false),
@@ -834,6 +837,7 @@ pub fn deposit_with_authority(
834837
deposit_stake_address: &Pubkey,
835838
deposit_stake_withdraw_authority: &Pubkey,
836839
validator_stake_account: &Pubkey,
840+
reserve_stake_account: &Pubkey,
837841
pool_tokens_to: &Pubkey,
838842
pool_mint: &Pubkey,
839843
token_program_id: &Pubkey,
@@ -845,6 +849,7 @@ pub fn deposit_with_authority(
845849
AccountMeta::new_readonly(*stake_pool_withdraw_authority, false),
846850
AccountMeta::new(*deposit_stake_address, false),
847851
AccountMeta::new(*validator_stake_account, false),
852+
AccountMeta::new(*reserve_stake_account, false),
848853
AccountMeta::new(*pool_tokens_to, false),
849854
AccountMeta::new(*pool_mint, false),
850855
AccountMeta::new_readonly(sysvar::clock::id(), false),

stake-pool/program/src/processor.rs

Lines changed: 105 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,47 @@ impl Processor {
357357
)
358358
}
359359

360+
/// Issue stake_program::withdraw instruction to move additional lamports
361+
#[allow(clippy::too_many_arguments)]
362+
fn stake_withdraw<'a>(
363+
stake_pool: &Pubkey,
364+
source_account: AccountInfo<'a>,
365+
authority: AccountInfo<'a>,
366+
authority_type: &[u8],
367+
bump_seed: u8,
368+
destination_account: AccountInfo<'a>,
369+
clock: AccountInfo<'a>,
370+
stake_history: AccountInfo<'a>,
371+
stake_program_info: AccountInfo<'a>,
372+
lamports: u64,
373+
) -> Result<(), ProgramError> {
374+
let me_bytes = stake_pool.to_bytes();
375+
let authority_signature_seeds = [&me_bytes[..32], authority_type, &[bump_seed]];
376+
let signers = &[&authority_signature_seeds[..]];
377+
let custodian_pubkey = None;
378+
379+
let withdraw_instruction = stake_program::withdraw(
380+
source_account.key,
381+
authority.key,
382+
destination_account.key,
383+
lamports,
384+
custodian_pubkey,
385+
);
386+
387+
invoke_signed(
388+
&withdraw_instruction,
389+
&[
390+
source_account,
391+
destination_account,
392+
clock,
393+
stake_history,
394+
authority,
395+
stake_program_info,
396+
],
397+
signers,
398+
)
399+
}
400+
360401
/// Issue a spl_token `Burn` instruction.
361402
#[allow(clippy::too_many_arguments)]
362403
fn token_burn<'a>(
@@ -1337,9 +1378,13 @@ impl Processor {
13371378
}
13381379
}
13391380
}
1340-
Some(stake_program::StakeState::Stake(_, stake)) => {
1381+
Some(stake_program::StakeState::Stake(meta, stake)) => {
1382+
let account_stake = meta
1383+
.rent_exempt_reserve
1384+
.checked_add(stake.delegation.stake)
1385+
.ok_or(StakePoolError::CalculationFailure)?;
13411386
if no_merge {
1342-
transient_stake_lamports = transient_stake_info.lamports();
1387+
transient_stake_lamports = account_stake;
13431388
} else if stake.delegation.deactivation_epoch < clock.epoch {
13441389
// deactivated, merge into reserve
13451390
Self::stake_merge(
@@ -1378,15 +1423,15 @@ impl Processor {
13781423
)?;
13791424
} else {
13801425
msg!("Stake activating or just active, not ready to merge");
1381-
transient_stake_lamports = transient_stake_info.lamports();
1426+
transient_stake_lamports = account_stake;
13821427
}
13831428
} else {
13841429
msg!("Transient stake is activating or active, but validator stake is not, need to add the validator stake account on {} back into the stake pool", stake.delegation.voter_pubkey);
1385-
transient_stake_lamports = transient_stake_info.lamports();
1430+
transient_stake_lamports = account_stake;
13861431
}
13871432
} else {
13881433
msg!("Transient stake not ready to be merged anywhere");
1389-
transient_stake_lamports = transient_stake_info.lamports();
1434+
transient_stake_lamports = account_stake;
13901435
}
13911436
}
13921437
None
@@ -1398,11 +1443,13 @@ impl Processor {
13981443
// * active -> do everything
13991444
// * any other state / not a stake -> error state, but account for transient stake
14001445
match validator_stake_state {
1401-
Some(stake_program::StakeState::Stake(meta, _)) => {
1446+
Some(stake_program::StakeState::Stake(_, stake)) => {
14021447
if validator_stake_record.status == StakeStatus::Active {
1403-
active_stake_lamports = validator_stake_info
1404-
.lamports()
1405-
.saturating_sub(minimum_stake_lamports(&meta));
1448+
active_stake_lamports = stake
1449+
.delegation
1450+
.stake
1451+
.checked_sub(MINIMUM_ACTIVE_STAKE)
1452+
.ok_or(StakePoolError::CalculationFailure)?;
14061453
} else {
14071454
msg!("Validator stake account no longer part of the pool, ignoring");
14081455
}
@@ -1566,6 +1613,7 @@ impl Processor {
15661613
let withdraw_authority_info = next_account_info(account_info_iter)?;
15671614
let stake_info = next_account_info(account_info_iter)?;
15681615
let validator_stake_account_info = next_account_info(account_info_iter)?;
1616+
let reserve_stake_account_info = next_account_info(account_info_iter)?;
15691617
let dest_user_info = next_account_info(account_info_iter)?;
15701618
let pool_mint_info = next_account_info(account_info_iter)?;
15711619
let clock_info = next_account_info(account_info_iter)?;
@@ -1594,6 +1642,7 @@ impl Processor {
15941642
stake_pool.check_deposit_authority(deposit_authority_info.key)?;
15951643
stake_pool.check_mint(pool_mint_info)?;
15961644
stake_pool.check_validator_list(validator_list_info)?;
1645+
stake_pool.check_reserve_stake(reserve_stake_account_info)?;
15971646

15981647
if stake_pool.token_program_id != *token_program_info.key {
15991648
return Err(ProgramError::IncorrectProgramId);
@@ -1610,8 +1659,9 @@ impl Processor {
16101659
return Err(StakePoolError::InvalidState.into());
16111660
}
16121661

1613-
let (meta, stake) = get_stake_state(validator_stake_account_info)?;
1614-
let vote_account_address = stake.delegation.voter_pubkey;
1662+
let (_, validator_stake) = get_stake_state(validator_stake_account_info)?;
1663+
let pre_all_validator_lamports = validator_stake_account_info.lamports();
1664+
let vote_account_address = validator_stake.delegation.voter_pubkey;
16151665
check_validator_stake_address(
16161666
program_id,
16171667
stake_pool_info.key,
@@ -1633,15 +1683,7 @@ impl Processor {
16331683
return Err(StakePoolError::ValidatorNotFound.into());
16341684
}
16351685

1636-
let stake_lamports = **stake_info.lamports.borrow();
1637-
let new_pool_tokens = stake_pool
1638-
.calc_pool_tokens_for_deposit(stake_lamports)
1639-
.ok_or(StakePoolError::CalculationFailure)?;
1640-
1641-
msg!(
1642-
"lamports pre merge {}",
1643-
validator_stake_account_info.lamports()
1644-
);
1686+
msg!("Stake pre merge {}", validator_stake.delegation.stake);
16451687

16461688
let (deposit_authority_program_address, deposit_bump_seed) =
16471689
find_deposit_authority_program_address(program_id, stake_pool_info.key);
@@ -1678,6 +1720,21 @@ impl Processor {
16781720
stake_program_info.clone(),
16791721
)?;
16801722

1723+
let (_, post_validator_stake) = get_stake_state(validator_stake_account_info)?;
1724+
let post_all_validator_lamports = validator_stake_account_info.lamports();
1725+
msg!("Stake post merge {}", post_validator_stake.delegation.stake);
1726+
let all_deposit_lamports = post_all_validator_lamports
1727+
.checked_sub(pre_all_validator_lamports)
1728+
.ok_or(StakePoolError::CalculationFailure)?;
1729+
let stake_deposit_lamports = post_validator_stake
1730+
.delegation
1731+
.stake
1732+
.checked_sub(validator_stake.delegation.stake)
1733+
.ok_or(StakePoolError::CalculationFailure)?;
1734+
let new_pool_tokens = stake_pool
1735+
.calc_pool_tokens_for_deposit(all_deposit_lamports)
1736+
.ok_or(StakePoolError::CalculationFailure)?;
1737+
16811738
Self::token_mint_to(
16821739
stake_pool_info.key,
16831740
token_program_info.clone(),
@@ -1689,23 +1746,39 @@ impl Processor {
16891746
new_pool_tokens,
16901747
)?;
16911748

1749+
// withdraw additional lamports to the reserve
1750+
let additional_lamports = all_deposit_lamports
1751+
.checked_sub(stake_deposit_lamports)
1752+
.ok_or(StakePoolError::CalculationFailure)?;
1753+
if additional_lamports > 0 {
1754+
Self::stake_withdraw(
1755+
stake_pool_info.key,
1756+
validator_stake_account_info.clone(),
1757+
withdraw_authority_info.clone(),
1758+
AUTHORITY_WITHDRAW,
1759+
stake_pool.withdraw_bump_seed,
1760+
reserve_stake_account_info.clone(),
1761+
clock_info.clone(),
1762+
stake_history_info.clone(),
1763+
stake_program_info.clone(),
1764+
additional_lamports,
1765+
)?;
1766+
}
1767+
16921768
stake_pool.pool_token_supply = stake_pool
16931769
.pool_token_supply
16941770
.checked_add(new_pool_tokens)
16951771
.ok_or(StakePoolError::CalculationFailure)?;
16961772
stake_pool.total_stake_lamports = stake_pool
16971773
.total_stake_lamports
1698-
.checked_add(stake_lamports)
1774+
.checked_add(all_deposit_lamports)
16991775
.ok_or(StakePoolError::CalculationFailure)?;
17001776
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;
17011777

1702-
msg!(
1703-
"lamports post merge {}",
1704-
validator_stake_account_info.lamports()
1705-
);
1706-
validator_list_item.active_stake_lamports = validator_stake_account_info
1707-
.lamports()
1708-
.checked_sub(minimum_stake_lamports(&meta))
1778+
validator_list_item.active_stake_lamports = post_validator_stake
1779+
.delegation
1780+
.stake
1781+
.checked_sub(MINIMUM_ACTIVE_STAKE)
17091782
.ok_or(StakePoolError::CalculationFailure)?;
17101783
validator_list.serialize(&mut *validator_list_info.data.borrow_mut())?;
17111784

@@ -1794,7 +1867,7 @@ impl Processor {
17941867
.ok_or(StakePoolError::StakeLamportsNotEqualToMinimum)?;
17951868
None
17961869
} else {
1797-
let (meta, stake) = get_stake_state(stake_split_from)?;
1870+
let (_, stake) = get_stake_state(stake_split_from)?;
17981871
let vote_account_address = stake.delegation.voter_pubkey;
17991872

18001873
if let Some(preferred_withdraw_validator) =
@@ -1840,11 +1913,9 @@ impl Processor {
18401913
return Err(StakePoolError::ValidatorNotFound.into());
18411914
}
18421915

1843-
let required_lamports = minimum_stake_lamports(&meta);
1844-
let current_lamports = stake_split_from.lamports();
1845-
let remaining_lamports = current_lamports.saturating_sub(withdraw_lamports);
1846-
if remaining_lamports < required_lamports {
1847-
msg!("Attempting to withdraw {} lamports from validator account with {} lamports, {} must remain", withdraw_lamports, current_lamports, required_lamports);
1916+
let remaining_lamports = stake.delegation.stake.saturating_sub(withdraw_lamports);
1917+
if remaining_lamports < MINIMUM_ACTIVE_STAKE {
1918+
msg!("Attempting to withdraw {} lamports from validator account with {} stake lamports, {} must remain", withdraw_lamports, stake.delegation.stake, MINIMUM_ACTIVE_STAKE);
18481919
return Err(StakePoolError::StakeLamportsNotEqualToMinimum.into());
18491920
}
18501921
Some((validator_list_item, withdrawing_from_transient_stake))

stake-pool/program/src/stake_program.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,29 @@ pub fn deactivate_stake(stake_pubkey: &Pubkey, authorized_pubkey: &Pubkey) -> In
645645
Instruction::new_with_bincode(id(), &StakeInstruction::Deactivate, account_metas)
646646
}
647647

648+
/// FIXME copied from the stake program
649+
pub fn withdraw(
650+
stake_pubkey: &Pubkey,
651+
withdrawer_pubkey: &Pubkey,
652+
to_pubkey: &Pubkey,
653+
lamports: u64,
654+
custodian_pubkey: Option<&Pubkey>,
655+
) -> Instruction {
656+
let mut account_metas = vec![
657+
AccountMeta::new(*stake_pubkey, false),
658+
AccountMeta::new(*to_pubkey, false),
659+
AccountMeta::new_readonly(sysvar::clock::id(), false),
660+
AccountMeta::new_readonly(sysvar::stake_history::id(), false),
661+
AccountMeta::new_readonly(*withdrawer_pubkey, true),
662+
];
663+
664+
if let Some(custodian_pubkey) = custodian_pubkey {
665+
account_metas.push(AccountMeta::new_readonly(*custodian_pubkey, true));
666+
}
667+
668+
Instruction::new_with_bincode(id(), &StakeInstruction::Withdraw(lamports), account_metas)
669+
}
670+
648671
#[cfg(test)]
649672
mod test {
650673
use {super::*, bincode::serialize, solana_program::borsh::try_from_slice_unchecked};

stake-pool/program/src/state.rs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use {
44
crate::error::StakePoolError,
55
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
66
solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey},
7-
spl_math::checked_ceil_div::CheckedCeilDiv,
87
std::convert::TryFrom,
98
};
109

@@ -99,13 +98,6 @@ impl StakePool {
9998
)
10099
.ok()
101100
}
102-
/// calculate the pool tokens that should be burned for a withdrawal of `stake_lamports`
103-
pub fn calc_pool_tokens_for_withdraw(&self, stake_lamports: u64) -> Option<u64> {
104-
let (quotient, _) = (stake_lamports as u128)
105-
.checked_mul(self.pool_token_supply as u128)?
106-
.checked_ceil_div(self.total_stake_lamports as u128)?;
107-
u64::try_from(quotient).ok()
108-
}
109101

110102
/// calculate lamports amount on withdrawal
111103
pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {

0 commit comments

Comments
 (0)