diff --git a/docs/src/stake-pool.md b/docs/src/stake-pool.md index 624894de612..44e8503fb30 100644 --- a/docs/src/stake-pool.md +++ b/docs/src/stake-pool.md @@ -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 diff --git a/stake-pool/program/src/processor.rs b/stake-pool/program/src/processor.rs index aff89ee6a96..b71063004e1 100644 --- a/stake-pool/program/src/processor.rs +++ b/stake-pool/program/src/processor.rs @@ -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::( 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())?; diff --git a/stake-pool/program/tests/deposit.rs b/stake-pool/program/tests/deposit.rs index d58f1511092..8ba464a122a 100644 --- a/stake-pool/program/tests/deposit.rs +++ b/stake-pool/program/tests/deposit.rs @@ -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::(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::()); + 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); } diff --git a/stake-pool/program/tests/helpers/mod.rs b/stake-pool/program/tests/helpers/mod.rs index ad0ede77923..bc9a8501816 100644 --- a/stake-pool/program/tests/helpers/mod.rs +++ b/stake-pool/program/tests/helpers/mod.rs @@ -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 } diff --git a/stake-pool/program/tests/withdraw.rs b/stake-pool/program/tests/withdraw.rs index b8ebbeb3c60..3b91023a185 100644 --- a/stake-pool/program/tests/withdraw.rs +++ b/stake-pool/program/tests/withdraw.rs @@ -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::(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::(&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 ); }