Skip to content

Commit b24202f

Browse files
committed
more work
1 parent e817e79 commit b24202f

1 file changed

Lines changed: 49 additions & 24 deletions

File tree

program/src/processor.rs

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,9 +1015,8 @@ impl Processor {
10151015
return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
10161016
}
10171017

1018-
let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1019-
1020-
let (pool_is_active, pool_is_activating) = {
1018+
let (pre_pool_stake, pool_is_active, pool_is_activating) = {
1019+
let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
10211020
let pool_stake_status = pool_stake_state
10221021
.delegation
10231022
.stake_activating_and_deactivating(
@@ -1027,6 +1026,7 @@ impl Processor {
10271026
);
10281027

10291028
(
1029+
pool_stake_state.delegation.stake,
10301030
is_stake_fully_active(&pool_stake_status),
10311031
is_stake_newly_activating(&pool_stake_status),
10321032
)
@@ -1050,10 +1050,7 @@ impl Processor {
10501050
// tokens for deposit are determined off the total stakeable value of both pool-owned accounts
10511051
let pre_total_nev = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
10521052

1053-
// we use the pool stake account to determine non-stake lamports to return to the user
1054-
let pre_pool_stake = pool_stake_state.delegation.stake;
10551053
let pre_user_lamports = user_stake_info.lamports();
1056-
10571054
let (user_stake_meta, user_stake_status) = match deserialize_stake(user_stake_info) {
10581055
Ok(StakeStateV2::Stake(meta, stake, _)) => (
10591056
meta,
@@ -1171,6 +1168,7 @@ impl Processor {
11711168
let stake_program_info = next_account_info(account_info_iter)?;
11721169

11731170
let rent = &Rent::get()?;
1171+
let minimum_delegation = stake::tools::get_minimum_delegation()?;
11741172

11751173
SinglePool::from_account_info(pool_info, program_id)?;
11761174

@@ -1201,26 +1199,56 @@ impl Processor {
12011199
// tokens for withdraw are determined off the total stakeable value of both pool-owned accounts
12021200
let pre_total_nev = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
12031201

1204-
// we deliberately do NOT validate the activation status of the pool account.
1205-
// neither snow nor rain nor warmup/cooldown nor validator delinquency prevents a user withdrawal
1206-
//
1207-
// NOTE this is fine for stake v4 but subtly wrong for stake v5 *if* the pool account was deactivated.
1208-
// stake v5 declines to (meaninglessly) adjust delegations of deactivated sources.
1209-
// this will (again) be correct with #581, which shifts to NEV accounting on lamports rather than stake.
1210-
// we should plan another SVSP release before stake v5 activation
1211-
let pre_pool_stake = get_stake_amount(pool_stake_info)?;
1212-
msg!("Available stake pre split {}", pre_pool_stake);
1213-
1214-
// withdraw amount is determined off stake just like deposit amount
1215-
let withdraw_stake = calculate_withdraw_amount(token_supply, pre_pool_nev, token_amount)
1202+
// note we deliberately do NOT validate the activation status of the pool account.
1203+
// neither warmup/cooldown nor validator delinquency prevent a user withdrawal.
1204+
// however, because we calculate NEV from all lamports in both pool accounts,
1205+
// but can only split stake from the main account (unless inactive), we must determine whether this is possible
1206+
let (withdrawable_value, pool_is_fully_inactive) = {
1207+
let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1208+
let pool_stake_status = pool_stake_state
1209+
.delegation
1210+
.stake_activating_and_deactivating(
1211+
clock.epoch,
1212+
stake_history,
1213+
PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1214+
);
1215+
1216+
// if fully inactuve, we split on lamports; otherwise, on all delegation.
1217+
// the stake program works off delegation in this way *even* for a partially deactivated stake
1218+
if pool_stake_status == StakeActivationStatus::default() {
1219+
(
1220+
pool_stake_info
1221+
.lamports()
1222+
.saturating_sub(rent.minimum_balance(pool_stake_info.data_len())),
1223+
true,
1224+
)
1225+
} else {
1226+
(pool_stake_state.delegation.stake, false)
1227+
}
1228+
};
1229+
1230+
// withdraw amount is determined off pool NEV just like deposit amount
1231+
let stake_to_withdraw = calculate_withdraw_amount(token_supply, pre_pool_nev, token_amount)
12161232
.ok_or(SinglePoolError::UnexpectedMathError)?;
12171233

1218-
if withdraw_stake == 0 {
1234+
// self-explanatory
1235+
if stake_to_withdraw == 0 {
12191236
return Err(SinglePoolError::WithdrawalTooSmall.into());
12201237
}
12211238

1222-
// the second case should never be true, but its best to be sure
1223-
if withdraw_stake > pre_pool_stake || withdraw_stake == pool_stake_info.lamports() {
1239+
// if the destination would be in any non-inactive state it must meet minimum delegation
1240+
if stake_to_withdraw < minimum_delegation {
1241+
return Err(SinglePoolError::WithdrawalTooSmall.into());
1242+
}
1243+
1244+
// if we do not have enough value to service this withdrawal, the user must wait a `ReplenishPool` cycle.
1245+
// this does *not* mean the value isnt in the pool, merely that it is not duly splittable
1246+
if stake_to_withdraw > withdrawable_value {
1247+
return Err(SinglePoolError::WithdrawalTooLarge.into());
1248+
}
1249+
1250+
// this is theoretically impossible but we guard becuase would put the pool in an unrecoverable state
1251+
if withdraw_stake == pool_stake_info.lamports() {
12241252
return Err(SinglePoolError::WithdrawalTooLarge.into());
12251253
}
12261254

@@ -1255,9 +1283,6 @@ impl Processor {
12551283
clock_info.clone(),
12561284
)?;
12571285

1258-
let post_pool_stake = get_stake_amount(pool_stake_info)?;
1259-
msg!("Available stake post split {}", post_pool_stake);
1260-
12611286
Ok(())
12621287
}
12631288

0 commit comments

Comments
 (0)