Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit f95e390

Browse files
authored
stake-pool: Clarify stake deposit fee (#2520)
1 parent 0a5e952 commit f95e390

File tree

5 files changed

+107
-71
lines changed

5 files changed

+107
-71
lines changed

docs/src/stake-pool.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,22 @@ $ spl-token balance BoNneHKDrX9BHjjvSpPfnQyRjsnc9WFH71v8wrgCd7LB
807807
10.00000000
808808
```
809809

810+
#### Note on stake deposit fee
811+
812+
Stake pools have separate fees for stake and SOL, so the total fee from depositing
813+
a stake account is calculated from the rent-exempt reserve as SOL, and the delegation
814+
as stake.
815+
816+
For example, if a stake pool has a stake deposit fee of 1%, and a SOL deposit fee
817+
of 5%, and you deposit a stake account with 10 SOL in stake, and .00228288 SOL
818+
in rent-exemption, the total fee charged is:
819+
820+
```
821+
total_fee = stake_delegation * stake_deposit_fee + rent_exemption * sol_deposit_fee
822+
total_fee = 10 * 1% + .00228288 * 5%
823+
total_fee = 0.100114144
824+
```
825+
810826
### Update
811827

812828
Every epoch, the network pays out rewards to stake accounts managed by the stake

stake-pool/program/src/processor.rs

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,8 +1784,6 @@ impl Processor {
17841784
return Err(StakePoolError::InvalidState.into());
17851785
}
17861786

1787-
//Self::check_stake_activation(stake_info, clock, stake_history)?;
1788-
17891787
stake_pool.check_authority_withdraw(
17901788
withdraw_authority_info.key,
17911789
program_id,
@@ -1836,16 +1834,6 @@ impl Processor {
18361834
}
18371835
}
18381836

1839-
let (meta, stake) = get_stake_state(stake_info)?;
1840-
1841-
// If the stake account is mergeable (full-activated), `meta.rent_exempt_reserve`
1842-
// will not be merged into `stake.delegation.stake`
1843-
let unactivated_stake_rent = if stake.delegation.activation_epoch < clock.epoch {
1844-
meta.rent_exempt_reserve
1845-
} else {
1846-
0
1847-
};
1848-
18491837
let mut validator_stake_info = validator_list
18501838
.find_mut::<ValidatorStakeInfo>(
18511839
vote_account_address.as_ref(),
@@ -1899,38 +1887,47 @@ impl Processor {
18991887
let post_all_validator_lamports = validator_stake_account_info.lamports();
19001888
msg!("Stake post merge {}", post_validator_stake.delegation.stake);
19011889

1902-
let all_deposit_lamports = post_all_validator_lamports
1890+
let total_deposit_lamports = post_all_validator_lamports
19031891
.checked_sub(pre_all_validator_lamports)
19041892
.ok_or(StakePoolError::CalculationFailure)?;
19051893
let stake_deposit_lamports = post_validator_stake
19061894
.delegation
19071895
.stake
19081896
.checked_sub(validator_stake.delegation.stake)
19091897
.ok_or(StakePoolError::CalculationFailure)?;
1910-
let additional_lamports = all_deposit_lamports
1898+
let sol_deposit_lamports = total_deposit_lamports
19111899
.checked_sub(stake_deposit_lamports)
19121900
.ok_or(StakePoolError::CalculationFailure)?;
1913-
let credited_additional_lamports = additional_lamports.min(unactivated_stake_rent);
1914-
let credited_deposit_lamports =
1915-
stake_deposit_lamports.saturating_add(credited_additional_lamports);
19161901

19171902
let new_pool_tokens = stake_pool
1918-
.calc_pool_tokens_for_deposit(credited_deposit_lamports)
1903+
.calc_pool_tokens_for_deposit(total_deposit_lamports)
1904+
.ok_or(StakePoolError::CalculationFailure)?;
1905+
let new_pool_tokens_from_stake = stake_pool
1906+
.calc_pool_tokens_for_deposit(stake_deposit_lamports)
1907+
.ok_or(StakePoolError::CalculationFailure)?;
1908+
let new_pool_tokens_from_sol = new_pool_tokens
1909+
.checked_sub(new_pool_tokens_from_stake)
19191910
.ok_or(StakePoolError::CalculationFailure)?;
19201911

1921-
let pool_tokens_stake_deposit_fee = stake_pool
1922-
.calc_pool_tokens_stake_deposit_fee(new_pool_tokens)
1912+
let stake_deposit_fee = stake_pool
1913+
.calc_pool_tokens_stake_deposit_fee(new_pool_tokens_from_stake)
1914+
.ok_or(StakePoolError::CalculationFailure)?;
1915+
let sol_deposit_fee = stake_pool
1916+
.calc_pool_tokens_sol_deposit_fee(new_pool_tokens_from_sol)
19231917
.ok_or(StakePoolError::CalculationFailure)?;
19241918

1919+
let total_fee = stake_deposit_fee
1920+
.checked_add(sol_deposit_fee)
1921+
.ok_or(StakePoolError::CalculationFailure)?;
19251922
let pool_tokens_user = new_pool_tokens
1926-
.checked_sub(pool_tokens_stake_deposit_fee)
1923+
.checked_sub(total_fee)
19271924
.ok_or(StakePoolError::CalculationFailure)?;
19281925

19291926
let pool_tokens_referral_fee = stake_pool
1930-
.calc_pool_tokens_stake_referral_fee(pool_tokens_stake_deposit_fee)
1927+
.calc_pool_tokens_stake_referral_fee(total_fee)
19311928
.ok_or(StakePoolError::CalculationFailure)?;
19321929

1933-
let pool_tokens_manager_deposit_fee = pool_tokens_stake_deposit_fee
1930+
let pool_tokens_manager_deposit_fee = total_fee
19341931
.checked_sub(pool_tokens_referral_fee)
19351932
.ok_or(StakePoolError::CalculationFailure)?;
19361933

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

1949-
if pool_tokens_user > 0 {
1950-
Self::token_mint_to(
1951-
stake_pool_info.key,
1952-
token_program_info.clone(),
1953-
pool_mint_info.clone(),
1954-
dest_user_pool_info.clone(),
1955-
withdraw_authority_info.clone(),
1956-
AUTHORITY_WITHDRAW,
1957-
stake_pool.stake_withdraw_bump_seed,
1958-
pool_tokens_user,
1959-
)?;
1960-
}
1946+
Self::token_mint_to(
1947+
stake_pool_info.key,
1948+
token_program_info.clone(),
1949+
pool_mint_info.clone(),
1950+
dest_user_pool_info.clone(),
1951+
withdraw_authority_info.clone(),
1952+
AUTHORITY_WITHDRAW,
1953+
stake_pool.stake_withdraw_bump_seed,
1954+
pool_tokens_user,
1955+
)?;
19611956
if pool_tokens_manager_deposit_fee > 0 {
19621957
Self::token_mint_to(
19631958
stake_pool_info.key,
@@ -1984,8 +1979,7 @@ impl Processor {
19841979
}
19851980

19861981
// withdraw additional lamports to the reserve
1987-
1988-
if additional_lamports > 0 {
1982+
if sol_deposit_lamports > 0 {
19891983
Self::stake_withdraw(
19901984
stake_pool_info.key,
19911985
validator_stake_account_info.clone(),
@@ -1996,7 +1990,7 @@ impl Processor {
19961990
clock_info.clone(),
19971991
stake_history_info.clone(),
19981992
stake_program_info.clone(),
1999-
additional_lamports,
1993+
sol_deposit_lamports,
20001994
)?;
20011995
}
20021996

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

stake-pool/program/tests/deposit.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,13 @@ async fn success() {
214214
// Check minted tokens
215215
let user_token_balance =
216216
get_token_balance(&mut context.banks_client, &pool_token_account).await;
217-
let tokens_issued_user =
218-
tokens_issued - stake_pool_accounts.calculate_deposit_fee(tokens_issued);
217+
let tokens_issued_user = tokens_issued
218+
- post_stake_pool
219+
.calc_pool_tokens_sol_deposit_fee(stake_rent)
220+
.unwrap()
221+
- post_stake_pool
222+
.calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
223+
.unwrap();
219224
assert_eq!(user_token_balance, tokens_issued_user);
220225

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

361-
let tokens_issued = stake_lamports;
366+
let tokens_issued = stake_lamports + extra_lamports;
362367
// For now tokens are 1:1 to stake
363368

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

389-
let tokens_issued_user =
390-
tokens_issued - stake_pool_accounts.calculate_deposit_fee(tokens_issued);
394+
let fee_tokens = post_stake_pool
395+
.calc_pool_tokens_sol_deposit_fee(extra_lamports + stake_rent)
396+
.unwrap()
397+
+ post_stake_pool
398+
.calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
399+
.unwrap();
400+
let tokens_issued_user = tokens_issued - fee_tokens;
391401
assert_eq!(user_token_balance, tokens_issued_user);
392402

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

396-
let tokens_issued_fees = stake_pool_accounts.calculate_deposit_fee(tokens_issued);
397-
let tokens_issued_referral_fee = stake_pool_accounts
398-
.calculate_referral_fee(stake_pool_accounts.calculate_deposit_fee(tokens_issued));
399-
let tokens_issued_manager_fee = tokens_issued_fees - tokens_issued_referral_fee;
406+
let referral_fee = stake_pool_accounts.calculate_referral_fee(fee_tokens);
407+
let manager_fee = fee_tokens - referral_fee;
400408

401-
assert_eq!(
402-
referrer_balance_post - referrer_balance_pre,
403-
tokens_issued_referral_fee
404-
);
409+
assert_eq!(referrer_balance_post - referrer_balance_pre, referral_fee);
405410

406411
let manager_pool_balance_post = get_token_balance(
407412
&mut context.banks_client,
@@ -410,7 +415,7 @@ async fn success_with_extra_stake_lamports() {
410415
.await;
411416
assert_eq!(
412417
manager_pool_balance_post - manager_pool_balance_pre,
413-
tokens_issued_manager_fee
418+
manager_fee
414419
);
415420

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

11151120
let referrer_balance_post =
11161121
get_token_balance(&mut context.banks_client, &referrer_token_account.pubkey()).await;
1117-
let referral_fee = stake_pool_accounts
1118-
.calculate_referral_fee(stake_pool_accounts.calculate_deposit_fee(stake_lamports));
1122+
let stake_pool = get_account(
1123+
&mut context.banks_client,
1124+
&stake_pool_accounts.stake_pool.pubkey(),
1125+
)
1126+
.await;
1127+
let stake_pool =
1128+
try_from_slice_unchecked::<state::StakePool>(stake_pool.data.as_slice()).unwrap();
1129+
let rent = context.banks_client.get_rent().await.unwrap();
1130+
let stake_rent = rent.minimum_balance(std::mem::size_of::<stake_program::StakeState>());
1131+
let fee_tokens = stake_pool
1132+
.calc_pool_tokens_sol_deposit_fee(stake_rent)
1133+
.unwrap()
1134+
+ stake_pool
1135+
.calc_pool_tokens_stake_deposit_fee(stake_lamports - stake_rent)
1136+
.unwrap();
1137+
let referral_fee = stake_pool_accounts.calculate_referral_fee(fee_tokens);
11191138
assert!(referral_fee > 0);
11201139
assert_eq!(referrer_balance_pre + referral_fee, referrer_balance_post);
11211140
}

stake-pool/program/tests/helpers/mod.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -685,10 +685,6 @@ impl StakePoolAccounts {
685685
pool_tokens * self.withdrawal_fee.numerator / self.withdrawal_fee.denominator
686686
}
687687

688-
pub fn calculate_deposit_fee(&self, pool_tokens: u64) -> u64 {
689-
pool_tokens * self.deposit_fee.numerator / self.deposit_fee.denominator
690-
}
691-
692688
pub fn calculate_referral_fee(&self, deposit_fee_collected: u64) -> u64 {
693689
deposit_fee_collected * self.referral_fee as u64 / 100
694690
}

stake-pool/program/tests/withdraw.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -994,15 +994,30 @@ async fn success_with_reserve() {
994994
assert!(error.is_none());
995995

996996
// first and only deposit, lamports:pool 1:1
997-
let tokens_deposit_fee =
998-
stake_pool_accounts.calculate_deposit_fee(deposit_info.stake_lamports + stake_rent);
999-
let tokens_withdrawal_fee =
1000-
stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens);
997+
let stake_pool = get_account(
998+
&mut context.banks_client,
999+
&stake_pool_accounts.stake_pool.pubkey(),
1000+
)
1001+
.await;
1002+
let stake_pool =
1003+
try_from_slice_unchecked::<state::StakePool>(stake_pool.data.as_slice()).unwrap();
1004+
// the entire deposit is actually stake since it isn't activated, so only
1005+
// the stake deposit fee is charged
1006+
let deposit_fee = stake_pool
1007+
.calc_pool_tokens_stake_deposit_fee(stake_rent + deposit_info.stake_lamports)
1008+
.unwrap();
10011009
assert_eq!(
1002-
deposit_info.stake_lamports + stake_rent - tokens_deposit_fee,
1010+
deposit_info.stake_lamports + stake_rent - deposit_fee,
10031011
deposit_info.pool_tokens,
1012+
"stake {} rent {} deposit fee {} pool tokens {}",
1013+
deposit_info.stake_lamports,
1014+
stake_rent,
1015+
deposit_fee,
1016+
deposit_info.pool_tokens
10041017
);
10051018

1019+
let withdrawal_fee = stake_pool_accounts.calculate_withdrawal_fee(deposit_info.pool_tokens);
1020+
10061021
// Check tokens used
10071022
let user_token_balance = get_token_balance(
10081023
&mut context.banks_client,
@@ -1020,12 +1035,8 @@ async fn success_with_reserve() {
10201035
let stake_state =
10211036
deserialize::<stake_program::StakeState>(&reserve_stake_account.data).unwrap();
10221037
let meta = stake_state.meta().unwrap();
1023-
// TODO: these numbers dont add up even with +tokens_deposit_fee
10241038
assert_eq!(
1025-
initial_reserve_lamports
1026-
+ meta.rent_exempt_reserve
1027-
+ tokens_withdrawal_fee
1028-
+ tokens_deposit_fee,
1039+
initial_reserve_lamports + meta.rent_exempt_reserve + withdrawal_fee + deposit_fee,
10291040
reserve_stake_account.lamports
10301041
);
10311042

@@ -1035,8 +1046,8 @@ async fn success_with_reserve() {
10351046
assert_eq!(
10361047
user_stake_recipient_account.lamports,
10371048
initial_stake_lamports + deposit_info.stake_lamports + stake_rent
1038-
- tokens_withdrawal_fee
1039-
- tokens_deposit_fee
1049+
- withdrawal_fee
1050+
- deposit_fee
10401051
);
10411052
}
10421053

0 commit comments

Comments
 (0)