Skip to content

Commit 5bcc433

Browse files
committed
program: require onramp to exist for deposit and withdraw
1 parent 8f9055c commit 5bcc433

3 files changed

Lines changed: 31 additions & 13 deletions

File tree

program/src/instruction.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub enum SinglePoolInstruction {
2727
/// Initialize the mint and main stake account for a new single-validator
2828
/// stake pool. The pool stake account must contain the rent-exempt
2929
/// minimum plus the minimum balance of 1 sol. No tokens will be minted;
30-
/// to deposit more, use `Deposit` after `InitializeStake`.
30+
/// to deposit more, use `Deposit` after `InitializeStake` and `InitializePoolOnRamp`.
3131
///
3232
/// 0. `[]` Validator vote account
3333
/// 1. `[w]` Pool account
@@ -119,7 +119,7 @@ pub enum SinglePoolInstruction {
119119
/// Create token metadata for the stake-pool token in the metaplex-token
120120
/// program. Step three of the permissionless three-stage initialization
121121
/// flow.
122-
/// Note this instruction is not necessary for the pool to operate, to
122+
/// Note this instruction is NOT necessary for the pool to operate, to
123123
/// ensure we cannot be broken by upstream.
124124
///
125125
/// 0. `[]` Pool account
@@ -156,7 +156,7 @@ pub enum SinglePoolInstruction {
156156
///
157157
/// New pools created with `initialize()` will include this instruction
158158
/// automatically. Existing pools must use `InitializePoolOnRamp` to upgrade to
159-
/// the latest version.
159+
/// the latest version. Note the on-ramp IS necessary for the pool to operate.
160160
///
161161
/// 0. `[]` Pool account
162162
/// 1. `[w]` Pool on-ramp account

program/src/processor.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,20 @@ fn pool_net_asset_value(
5050
let pool_rent_exempt_reserve = rent.minimum_balance(pool_stake_info.data_len());
5151
let onramp_rent_exempt_reserve = rent.minimum_balance(pool_onramp_info.data_len());
5252

53-
// NEV is all lamports in both accounts less rent
54-
pool_stake_info
53+
// NAV is all lamports in both accounts less rent
54+
55+
let main_stake_value = pool_stake_info
56+
.lamports()
57+
.saturating_sub(pool_rent_exempt_reserve);
58+
59+
let onramp_value = pool_onramp_info
5560
.lamports()
56-
.saturating_add(pool_onramp_info.lamports())
57-
.saturating_sub(pool_rent_exempt_reserve)
58-
.saturating_sub(onramp_rent_exempt_reserve)
61+
.saturating_sub(onramp_rent_exempt_reserve);
62+
63+
main_stake_value.saturating_add(onramp_value)
5964
}
6065

61-
/// Calculate pool tokens to mint, given outstanding token supply, pool NEV, and deposit amount
66+
/// Calculate pool tokens to mint, given outstanding token supply, pool NAV, and deposit amount
6267
fn calculate_deposit_amount(
6368
pre_token_supply: u64,
6469
pre_pool_nev: u64,
@@ -76,7 +81,7 @@ fn calculate_deposit_amount(
7681
}
7782
}
7883

79-
/// Calculate pool value to return, given outstanding token supply, pool NEV, and tokens to redeem
84+
/// Calculate pool value to return, given outstanding token supply, pool NAV, and tokens to redeem
8085
fn calculate_withdraw_amount(
8186
pre_token_supply: u64,
8287
pre_pool_nev: u64,
@@ -1048,6 +1053,12 @@ impl Processor {
10481053
unreachable!();
10491054
};
10501055

1056+
// onramp must exist
1057+
match deserialize_stake(pool_onramp_info) {
1058+
Ok(StakeStateV2::Initialized(_)) | Ok(StakeStateV2::Stake(_, _, _)) => (),
1059+
_ => return Err(SinglePoolError::OnRampDoesntExist.into()),
1060+
};
1061+
10511062
// tokens for deposit are determined off the total stakeable value of both pool-owned accounts
10521063
let pre_total_nev = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
10531064

@@ -1209,7 +1220,7 @@ impl Processor {
12091220

12101221
// note we deliberately do NOT validate the activation status of the pool account.
12111222
// neither warmup/cooldown nor validator delinquency prevent a user withdrawal.
1212-
// however, because we calculate NEV from all lamports in both pool accounts,
1223+
// however, because we calculate NAV from all lamports in both pool accounts,
12131224
// but can only split stake from the main account (unless inactive), we must determine whether this is possible
12141225
let (withdrawable_value, pool_is_fully_inactive) = {
12151226
let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
@@ -1235,7 +1246,14 @@ impl Processor {
12351246
}
12361247
};
12371248

1238-
// withdraw amount is determined off pool NEV just like deposit amount
1249+
// onramp must exist. this does not create an edge case where withdrawals may be blocked,
1250+
// because we also require the onramp to exist for deposits
1251+
match deserialize_stake(pool_onramp_info) {
1252+
Ok(StakeStateV2::Initialized(_)) | Ok(StakeStateV2::Stake(_, _, _)) => (),
1253+
_ => return Err(SinglePoolError::OnRampDoesntExist.into()),
1254+
};
1255+
1256+
// withdraw amount is determined off pool NAV just like deposit amount
12391257
let stake_to_withdraw =
12401258
calculate_withdraw_amount(token_supply, pre_total_nev, token_amount)
12411259
.ok_or(SinglePoolError::UnexpectedMathError)?;

program/tests/withdraw.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ async fn fail_disallowed_withdraw(stake_version: StakeProgramVersion) {
431431
.unwrap_err();
432432
check_error(e, SinglePoolError::WithdrawalTooSmall);
433433

434-
// pump NEV higher. token is worth more but mostly backed by liquid sol
434+
// pump NAV higher. token is worth more but mostly backed by liquid sol
435435
transfer(
436436
&mut context.banks_client,
437437
&context.payer,

0 commit comments

Comments
 (0)