@@ -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