stake-pool: Clarify stake deposit fee (#2520)
This commit is contained in:
parent
0a5e952485
commit
f95e390dd4
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -1899,7 +1887,7 @@ 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
|
||||
|
@ -1907,30 +1895,39 @@ impl Processor {
|
|||
.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)?;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -1996,7 +1990,7 @@ impl Processor {
|
|||
clock_info.clone(),
|
||||
stake_history_info.clone(),
|
||||
stake_program_info.clone(),
|
||||
additional_lamports,
|
||||
sol_deposit_lamports,
|
||||
)?;
|
||||
}
|
||||
|
||||
|
@ -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())?;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue