Skip to content

Commit cf872d1

Browse files
committed
more work
1 parent e817e79 commit cf872d1

1 file changed

Lines changed: 55 additions & 27 deletions

File tree

program/src/processor.rs

Lines changed: 55 additions & 27 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,
@@ -1167,10 +1164,13 @@ impl Processor {
11671164
let user_stake_info = next_account_info(account_info_iter)?;
11681165
let user_token_account_info = next_account_info(account_info_iter)?;
11691166
let clock_info = next_account_info(account_info_iter)?;
1167+
let clock = &Clock::from_account_info(clock_info)?;
11701168
let token_program_info = next_account_info(account_info_iter)?;
11711169
let stake_program_info = next_account_info(account_info_iter)?;
11721170

11731171
let rent = &Rent::get()?;
1172+
let stake_history = &StakeHistorySysvar(clock.epoch);
1173+
let minimum_delegation = stake::tools::get_minimum_delegation()?;
11741174

11751175
SinglePool::from_account_info(pool_info, program_id)?;
11761176

@@ -1201,26 +1201,57 @@ impl Processor {
12011201
// tokens for withdraw are determined off the total stakeable value of both pool-owned accounts
12021202
let pre_total_nev = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
12031203

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)
1216-
.ok_or(SinglePoolError::UnexpectedMathError)?;
1217-
1218-
if withdraw_stake == 0 {
1204+
// note we deliberately do NOT validate the activation status of the pool account.
1205+
// neither warmup/cooldown nor validator delinquency prevent a user withdrawal.
1206+
// however, because we calculate NEV from all lamports in both pool accounts,
1207+
// but can only split stake from the main account (unless inactive), we must determine whether this is possible
1208+
let (withdrawable_value, pool_is_fully_inactive) = {
1209+
let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1210+
let pool_stake_status = pool_stake_state
1211+
.delegation
1212+
.stake_activating_and_deactivating(
1213+
clock.epoch,
1214+
stake_history,
1215+
PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1216+
);
1217+
1218+
// if fully inactuve, we split on lamports; otherwise, on all delegation.
1219+
// the stake program works off delegation in this way *even* for a partially deactivated stake
1220+
if pool_stake_status == StakeActivationStatus::default() {
1221+
(
1222+
pool_stake_info
1223+
.lamports()
1224+
.saturating_sub(rent.minimum_balance(pool_stake_info.data_len())),
1225+
true,
1226+
)
1227+
} else {
1228+
(pool_stake_state.delegation.stake, false)
1229+
}
1230+
};
1231+
1232+
// withdraw amount is determined off pool NEV just like deposit amount
1233+
let stake_to_withdraw =
1234+
calculate_withdraw_amount(token_supply, pre_total_nev, token_amount)
1235+
.ok_or(SinglePoolError::UnexpectedMathError)?;
1236+
1237+
// self-explanatory
1238+
if stake_to_withdraw == 0 {
12191239
return Err(SinglePoolError::WithdrawalTooSmall.into());
12201240
}
12211241

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() {
1242+
// if the destination would be in any non-inactive state it must meet minimum delegation
1243+
if !pool_is_fully_inactive && stake_to_withdraw < minimum_delegation {
1244+
return Err(SinglePoolError::WithdrawalTooSmall.into());
1245+
}
1246+
1247+
// if we do not have enough value to service this withdrawal, the user must wait a `ReplenishPool` cycle.
1248+
// this does *not* mean the value isnt in the pool, merely that it is not duly splittable
1249+
if stake_to_withdraw > withdrawable_value {
1250+
return Err(SinglePoolError::WithdrawalTooLarge.into());
1251+
}
1252+
1253+
// this is theoretically impossible but we guard becuase would put the pool in an unrecoverable state
1254+
if stake_to_withdraw == pool_stake_info.lamports() {
12241255
return Err(SinglePoolError::WithdrawalTooLarge.into());
12251256
}
12261257

@@ -1241,7 +1272,7 @@ impl Processor {
12411272
pool_stake_info.clone(),
12421273
pool_stake_authority_info.clone(),
12431274
stake_authority_bump_seed,
1244-
withdraw_stake,
1275+
stake_to_withdraw,
12451276
user_stake_info.clone(),
12461277
)?;
12471278

@@ -1255,9 +1286,6 @@ impl Processor {
12551286
clock_info.clone(),
12561287
)?;
12571288

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

0 commit comments

Comments
 (0)