Skip to content

Commit

Permalink
stake-pool: Clarify stake deposit fee (solana-labs#2520)
Browse files Browse the repository at this point in the history
  • Loading branch information
joncinque authored Oct 19, 2021
1 parent 0a5e952 commit f95e390
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 71 deletions.
16 changes: 16 additions & 0 deletions docs/src/stake-pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,22 @@ $ spl-token balance BoNneHKDrX9BHjjvSpPfnQyRjsnc9WFH71v8wrgCd7LB
10.00000000
```

#### Note on stake deposit fee

Stake pools have separate fees for stake and SOL, so the total fee from depositing
a stake account is calculated from the rent-exempt reserve as SOL, and the delegation
as stake.

For example, if a stake pool has a stake deposit fee of 1%, and a SOL deposit fee
of 5%, and you deposit a stake account with 10 SOL in stake, and .00228288 SOL
in rent-exemption, the total fee charged is:

```
total_fee = stake_delegation * stake_deposit_fee + rent_exemption * sol_deposit_fee
total_fee = 10 * 1% + .00228288 * 5%
total_fee = 0.100114144
```

### Update

Every epoch, the network pays out rewards to stake accounts managed by the stake
Expand Down
72 changes: 33 additions & 39 deletions stake-pool/program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1784,8 +1784,6 @@ impl Processor {
return Err(StakePoolError::InvalidState.into());
}

//Self::check_stake_activation(stake_info, clock, stake_history)?;

stake_pool.check_authority_withdraw(
withdraw_authority_info.key,
program_id,
Expand Down Expand Up @@ -1836,16 +1834,6 @@ impl Processor {
}
}

let (meta, stake) = get_stake_state(stake_info)?;

// If the stake account is mergeable (full-activated), `meta.rent_exempt_reserve`
// will not be merged into `stake.delegation.stake`
let unactivated_stake_rent = if stake.delegation.activation_epoch < clock.epoch {
meta.rent_exempt_reserve
} else {
0
};

let mut validator_stake_info = validator_list
.find_mut::<ValidatorStakeInfo>(
vote_account_address.as_ref(),
Expand Down Expand Up @@ -1899,38 +1887,47 @@ impl Processor {
let post_all_validator_lamports = validator_stake_account_info.lamports();
msg!("Stake post merge {}", post_validator_stake.delegation.stake);

let all_deposit_lamports = post_all_validator_lamports
let total_deposit_lamports = post_all_validator_lamports
.checked_sub(pre_all_validator_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
let stake_deposit_lamports = post_validator_stake
.delegation
.stake
.checked_sub(validator_stake.delegation.stake)
.ok_or(StakePoolError::CalculationFailure)?;
let additional_lamports = all_deposit_lamports
let sol_deposit_lamports = total_deposit_lamports
.checked_sub(stake_deposit_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
let credited_additional_lamports = additional_lamports.min(unactivated_stake_rent);
let credited_deposit_lamports =
stake_deposit_lamports.saturating_add(credited_additional_lamports);

let new_pool_tokens = stake_pool
.calc_pool_tokens_for_deposit(credited_deposit_lamports)
.calc_pool_tokens_for_deposit(total_deposit_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
let new_pool_tokens_from_stake = stake_pool
.calc_pool_tokens_for_deposit(stake_deposit_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
let new_pool_tokens_from_sol = new_pool_tokens
.checked_sub(new_pool_tokens_from_stake)
.ok_or(StakePoolError::CalculationFailure)?;

let pool_tokens_stake_deposit_fee = stake_pool
.calc_pool_tokens_stake_deposit_fee(new_pool_tokens)
let stake_deposit_fee = stake_pool
.calc_pool_tokens_stake_deposit_fee(new_pool_tokens_from_stake)
.ok_or(StakePoolError::CalculationFailure)?;
let sol_deposit_fee = stake_pool
.calc_pool_tokens_sol_deposit_fee(new_pool_tokens_from_sol)
.ok_or(StakePoolError::CalculationFailure)?;

let total_fee = stake_deposit_fee
.checked_add(sol_deposit_fee)
.ok_or(StakePoolError::CalculationFailure)?;
let pool_tokens_user = new_pool_tokens
.checked_sub(pool_tokens_stake_deposit_fee)
.checked_sub(total_fee)
.ok_or(StakePoolError::CalculationFailure)?;

let pool_tokens_referral_fee = stake_pool
.calc_pool_tokens_stake_referral_fee(pool_tokens_stake_deposit_fee)
.calc_pool_tokens_stake_referral_fee(total_fee)
.ok_or(StakePoolError::CalculationFailure)?;

let pool_tokens_manager_deposit_fee = pool_tokens_stake_deposit_fee
let pool_tokens_manager_deposit_fee = total_fee
.checked_sub(pool_tokens_referral_fee)
.ok_or(StakePoolError::CalculationFailure)?;

Expand All @@ -1946,18 +1943,16 @@ impl Processor {
return Err(StakePoolError::DepositTooSmall.into());
}

if pool_tokens_user > 0 {
Self::token_mint_to(
stake_pool_info.key,
token_program_info.clone(),
pool_mint_info.clone(),
dest_user_pool_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
pool_tokens_user,
)?;
}
Self::token_mint_to(
stake_pool_info.key,
token_program_info.clone(),
pool_mint_info.clone(),
dest_user_pool_info.clone(),
withdraw_authority_info.clone(),
AUTHORITY_WITHDRAW,
stake_pool.stake_withdraw_bump_seed,
pool_tokens_user,
)?;
if pool_tokens_manager_deposit_fee > 0 {
Self::token_mint_to(
stake_pool_info.key,
Expand All @@ -1984,8 +1979,7 @@ impl Processor {
}

// withdraw additional lamports to the reserve

if additional_lamports > 0 {
if sol_deposit_lamports > 0 {
Self::stake_withdraw(
stake_pool_info.key,
validator_stake_account_info.clone(),
Expand All @@ -1996,7 +1990,7 @@ impl Processor {
clock_info.clone(),
stake_history_info.clone(),
stake_program_info.clone(),
additional_lamports,
sol_deposit_lamports,
)?;
}

Expand All @@ -2008,7 +2002,7 @@ impl Processor {
// transferred directly to the reserve stake account.
stake_pool.total_lamports = stake_pool
.total_lamports
.checked_add(all_deposit_lamports)
.checked_add(total_deposit_lamports)
.ok_or(StakePoolError::CalculationFailure)?;
stake_pool.serialize(&mut *stake_pool_info.data.borrow_mut())?;

Expand Down
51 changes: 35 additions & 16 deletions stake-pool/program/tests/deposit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,13 @@ async fn success() {
// Check minted tokens
let user_token_balance =
get_token_balance(&mut context.banks_client, &pool_token_account).await;
let tokens_issued_user =
tokens_issued - stake_pool_accounts.calculate_deposit_fee(tokens_issued);
let tokens_issued_user = tokens_issued
- post_stake_pool
.calc_pool_tokens_sol_deposit_fee(stake_rent)
.unwrap()
- post_stake_pool
.calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
.unwrap();
assert_eq!(user_token_balance, tokens_issued_user);

// Check balances in validator stake account list storage
Expand Down Expand Up @@ -358,7 +363,7 @@ async fn success_with_extra_stake_lamports() {
.expect("get_account")
.is_none());

let tokens_issued = stake_lamports;
let tokens_issued = stake_lamports + extra_lamports;
// For now tokens are 1:1 to stake

// Stake pool should add its balance to the pool balance
Expand Down Expand Up @@ -386,22 +391,22 @@ async fn success_with_extra_stake_lamports() {
let user_token_balance =
get_token_balance(&mut context.banks_client, &pool_token_account).await;

let tokens_issued_user =
tokens_issued - stake_pool_accounts.calculate_deposit_fee(tokens_issued);
let fee_tokens = post_stake_pool
.calc_pool_tokens_sol_deposit_fee(extra_lamports + stake_rent)
.unwrap()
+ post_stake_pool
.calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
.unwrap();
let tokens_issued_user = tokens_issued - fee_tokens;
assert_eq!(user_token_balance, tokens_issued_user);

let referrer_balance_post =
get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;

let tokens_issued_fees = stake_pool_accounts.calculate_deposit_fee(tokens_issued);
let tokens_issued_referral_fee = stake_pool_accounts
.calculate_referral_fee(stake_pool_accounts.calculate_deposit_fee(tokens_issued));
let tokens_issued_manager_fee = tokens_issued_fees - tokens_issued_referral_fee;
let referral_fee = stake_pool_accounts.calculate_referral_fee(fee_tokens);
let manager_fee = fee_tokens - referral_fee;

assert_eq!(
referrer_balance_post - referrer_balance_pre,
tokens_issued_referral_fee
);
assert_eq!(referrer_balance_post - referrer_balance_pre, referral_fee);

let manager_pool_balance_post = get_token_balance(
&mut context.banks_client,
Expand All @@ -410,7 +415,7 @@ async fn success_with_extra_stake_lamports() {
.await;
assert_eq!(
manager_pool_balance_post - manager_pool_balance_pre,
tokens_issued_manager_fee
manager_fee
);

// Check balances in validator stake account list storage
Expand Down Expand Up @@ -1114,8 +1119,22 @@ async fn success_with_referral_fee() {

let referrer_balance_post =
get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;
let referral_fee = stake_pool_accounts
.calculate_referral_fee(stake_pool_accounts.calculate_deposit_fee(stake_lamports));
let stake_pool = get_account(
&mut context.banks_client,
&stake_pool_accounts.stake_pool.pubkey(),
)
.await;
let stake_pool =
try_from_slice_unchecked::<state::StakePool>(stake_pool.data.as_slice()).unwrap();
let rent = context.banks_client.get_rent().await.unwrap();
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
let fee_tokens = stake_pool
.calc_pool_tokens_sol_deposit_fee(stake_rent)
.unwrap()
+ stake_pool
.calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
.unwrap();
let referral_fee = stake_pool_accounts.calculate_referral_fee(fee_tokens);
assert!(referral_fee > 0);
assert_eq!(referrer_balance_pre + referral_fee, referrer_balance_post);
}
Expand Down
4 changes: 0 additions & 4 deletions stake-pool/program/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,10 +685,6 @@ impl StakePoolAccounts {
pool_tokens * self.withdrawal_fee.numerator / self.withdrawal_fee.denominator
}

pub fn calculate_deposit_fee(&self, pool_tokens: u64) -> u64 {
pool_tokens * self.deposit_fee.numerator / self.deposit_fee.denominator
}

pub fn calculate_referral_fee(&self, deposit_fee_collected: u64) -> u64 {
deposit_fee_collected * self.referral_fee as u64 / 100
}
Expand Down
35 changes: 23 additions & 12 deletions stake-pool/program/tests/withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -994,15 +994,30 @@ async fn success_with_reserve() {
assert!(error.is_none());

// first and only deposit, lamports:pool 1:1
let tokens_deposit_fee =
stake_pool_accounts.calculate_deposit_fee(deposit_info.stake_lamports + stake_rent);
let tokens_withdrawal_fee =
stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens);
let stake_pool = get_account(
&mut context.banks_client,
&stake_pool_accounts.stake_pool.pubkey(),
)
.await;
let stake_pool =
try_from_slice_unchecked::<state::StakePool>(stake_pool.data.as_slice()).unwrap();
// the entire deposit is actually stake since it isn't activated, so only
// the stake deposit fee is charged
let deposit_fee = stake_pool
.calc_pool_tokens_stake_deposit_fee(stake_rent + deposit_info.stake_lamports)
.unwrap();
assert_eq!(
deposit_info.stake_lamports + stake_rent - tokens_deposit_fee,
deposit_info.stake_lamports + stake_rent - deposit_fee,
deposit_info.pool_tokens,
"stake {} rent {} deposit fee {} pool tokens {}",
deposit_info.stake_lamports,
stake_rent,
deposit_fee,
deposit_info.pool_tokens
);

let withdrawal_fee = stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens);

// Check tokens used
let user_token_balance = get_token_balance(
&mut context.banks_client,
Expand All @@ -1020,12 +1035,8 @@ async fn success_with_reserve() {
let stake_state =
deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap();
let meta = stake_state.meta().unwrap();
// TODO: these numbers dont add up even with +tokens_deposit_fee
assert_eq!(
initial_reserve_lamports
+ meta.rent_exempt_reserve
+ tokens_withdrawal_fee
+ tokens_deposit_fee,
initial_reserve_lamports + meta.rent_exempt_reserve + withdrawal_fee + deposit_fee,
reserve_stake_account.lamports
);

Expand All @@ -1035,8 +1046,8 @@ async fn success_with_reserve() {
assert_eq!(
user_stake_recipient_account.lamports,
initial_stake_lamports + deposit_info.stake_lamports + stake_rent
- tokens_withdrawal_fee
- tokens_deposit_fee
- withdrawal_fee
- deposit_fee
);
}

Expand Down

0 comments on commit f95e390

Please sign in to comment.