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