@@ -86,12 +86,12 @@ pub struct StakePool {
8686 /// Total stake under management.
8787 /// Note that if `last_update_epoch` does not match the current epoch then
8888 /// this field may not be accurate
89- pub total_stake_lamports : u64 ,
89+ pub total_lamports : u64 ,
9090
9191 /// Total supply of pool tokens (should always match the supply in the Pool Mint)
9292 pub pool_token_supply : u64 ,
9393
94- /// Last epoch the `total_stake_lamports ` field was updated
94+ /// Last epoch the `total_lamports ` field was updated
9595 pub last_update_epoch : u64 ,
9696
9797 /// Lockup that all stakes in the pool must have
@@ -146,18 +146,24 @@ pub struct StakePool {
146146
147147 /// Future SOL withdrawal fee, to be set for the following epoch
148148 pub next_sol_withdrawal_fee : Option < Fee > ,
149+
150+ /// Last epoch's total pool tokens, used only for APR estimation
151+ pub last_epoch_pool_token_supply : u64 ,
152+
153+ /// Last epoch's total lamports, used only for APR estimation
154+ pub last_epoch_total_lamports : u64 ,
149155}
150156impl StakePool {
151157 /// calculate the pool tokens that should be minted for a deposit of `stake_lamports`
152158 #[ inline]
153159 pub fn calc_pool_tokens_for_deposit ( & self , stake_lamports : u64 ) -> Option < u64 > {
154- if self . total_stake_lamports == 0 || self . pool_token_supply == 0 {
160+ if self . total_lamports == 0 || self . pool_token_supply == 0 {
155161 return Some ( stake_lamports) ;
156162 }
157163 u64:: try_from (
158164 ( stake_lamports as u128 )
159165 . checked_mul ( self . pool_token_supply as u128 ) ?
160- . checked_div ( self . total_stake_lamports as u128 ) ?,
166+ . checked_div ( self . total_lamports as u128 ) ?,
161167 )
162168 . ok ( )
163169 }
@@ -168,7 +174,7 @@ impl StakePool {
168174 // `checked_ceil_div` returns `None` for a 0 quotient result, but in this
169175 // case, a return of 0 is valid for small amounts of pool tokens. So
170176 // we check for that separately
171- let numerator = ( pool_tokens as u128 ) . checked_mul ( self . total_stake_lamports as u128 ) ?;
177+ let numerator = ( pool_tokens as u128 ) . checked_mul ( self . total_lamports as u128 ) ?;
172178 let denominator = self . pool_token_supply as u128 ;
173179 if numerator < denominator || denominator == 0 {
174180 Some ( 0 )
@@ -227,22 +233,21 @@ impl StakePool {
227233 /// Calculate the fee in pool tokens that goes to the manager
228234 ///
229235 /// This function assumes that `reward_lamports` has not already been added
230- /// to the stake pool's `total_stake_lamports `
236+ /// to the stake pool's `total_lamports `
231237 #[ inline]
232238 pub fn calc_epoch_fee_amount ( & self , reward_lamports : u64 ) -> Option < u64 > {
233239 if reward_lamports == 0 {
234240 return Some ( 0 ) ;
235241 }
236- let total_stake_lamports =
237- ( self . total_stake_lamports as u128 ) . checked_add ( reward_lamports as u128 ) ?;
242+ let total_lamports = ( self . total_lamports as u128 ) . checked_add ( reward_lamports as u128 ) ?;
238243 let fee_lamports = self . epoch_fee . apply ( reward_lamports) ?;
239- if total_stake_lamports == fee_lamports || self . pool_token_supply == 0 {
244+ if total_lamports == fee_lamports || self . pool_token_supply == 0 {
240245 Some ( reward_lamports)
241246 } else {
242247 u64:: try_from (
243248 ( self . pool_token_supply as u128 )
244249 . checked_mul ( fee_lamports) ?
245- . checked_div ( total_stake_lamports . checked_sub ( fee_lamports) ?) ?,
250+ . checked_div ( total_lamports . checked_sub ( fee_lamports) ?) ?,
246251 )
247252 . ok ( )
248253 }
@@ -816,7 +821,10 @@ mod test {
816821 solana_program:: borsh:: {
817822 get_instance_packed_len, get_packed_len, try_from_slice_unchecked,
818823 } ,
819- solana_program:: native_token:: LAMPORTS_PER_SOL ,
824+ solana_program:: {
825+ clock:: { DEFAULT_SLOTS_PER_EPOCH , DEFAULT_S_PER_SLOT , SECONDS_PER_DAY } ,
826+ native_token:: LAMPORTS_PER_SOL ,
827+ } ,
820828 } ;
821829
822830 fn uninitialized_validator_list ( ) -> ValidatorList {
@@ -992,11 +1000,11 @@ mod test {
9921000 }
9931001
9941002 prop_compose ! {
995- fn total_stake_and_rewards( ) ( total_stake_lamports in 1 ..u64 :: MAX ) (
996- total_stake_lamports in Just ( total_stake_lamports ) ,
997- rewards in 0 ..=total_stake_lamports ,
1003+ fn total_stake_and_rewards( ) ( total_lamports in 1 ..u64 :: MAX ) (
1004+ total_lamports in Just ( total_lamports ) ,
1005+ rewards in 0 ..=total_lamports ,
9981006 ) -> ( u64 , u64 ) {
999- ( total_stake_lamports - rewards, rewards)
1007+ ( total_lamports - rewards, rewards)
10001008 }
10011009 }
10021010
@@ -1008,15 +1016,15 @@ mod test {
10081016 denominator : 10 ,
10091017 } ;
10101018 let mut stake_pool = StakePool {
1011- total_stake_lamports : 100 * LAMPORTS_PER_SOL ,
1019+ total_lamports : 100 * LAMPORTS_PER_SOL ,
10121020 pool_token_supply : 100 * LAMPORTS_PER_SOL ,
10131021 epoch_fee,
10141022 ..StakePool :: default ( )
10151023 } ;
10161024 let reward_lamports = 10 * LAMPORTS_PER_SOL ;
10171025 let pool_token_fee = stake_pool. calc_epoch_fee_amount ( reward_lamports) . unwrap ( ) ;
10181026
1019- stake_pool. total_stake_lamports += reward_lamports;
1027+ stake_pool. total_lamports += reward_lamports;
10201028 stake_pool. pool_token_supply += pool_token_fee;
10211029
10221030 let fee_lamports = stake_pool
@@ -1042,7 +1050,7 @@ mod test {
10421050 #[ test]
10431051 fn divide_by_zero_fee ( ) {
10441052 let stake_pool = StakePool {
1045- total_stake_lamports : 0 ,
1053+ total_lamports : 0 ,
10461054 epoch_fee : Fee {
10471055 numerator : 1 ,
10481056 denominator : 10 ,
@@ -1054,22 +1062,44 @@ mod test {
10541062 assert_eq ! ( fee, rewards) ;
10551063 }
10561064
1065+ #[ test]
1066+ fn approximate_apr_calculation ( ) {
1067+ // 8% / year means roughly .044% / epoch
1068+ let stake_pool = StakePool {
1069+ last_epoch_total_lamports : 100_000 ,
1070+ last_epoch_pool_token_supply : 100_000 ,
1071+ total_lamports : 100_044 ,
1072+ pool_token_supply : 100_000 ,
1073+ ..StakePool :: default ( )
1074+ } ;
1075+ let pool_token_value =
1076+ stake_pool. total_lamports as f64 / stake_pool. pool_token_supply as f64 ;
1077+ let last_epoch_pool_token_value = stake_pool. last_epoch_total_lamports as f64
1078+ / stake_pool. last_epoch_pool_token_supply as f64 ;
1079+ let epoch_rate = pool_token_value / last_epoch_pool_token_value - 1.0 ;
1080+ const SECONDS_PER_EPOCH : f64 = DEFAULT_SLOTS_PER_EPOCH as f64 * DEFAULT_S_PER_SLOT ;
1081+ const EPOCHS_PER_YEAR : f64 = SECONDS_PER_DAY as f64 * 365.25 / SECONDS_PER_EPOCH ;
1082+ const EPSILON : f64 = 0.00001 ;
1083+ let yearly_rate = epoch_rate * EPOCHS_PER_YEAR ;
1084+ assert ! ( ( yearly_rate - 0.080355 ) . abs( ) < EPSILON ) ;
1085+ }
1086+
10571087 proptest ! {
10581088 #[ test]
10591089 fn fee_calculation(
10601090 ( numerator, denominator) in fee( ) ,
1061- ( total_stake_lamports , reward_lamports) in total_stake_and_rewards( ) ,
1091+ ( total_lamports , reward_lamports) in total_stake_and_rewards( ) ,
10621092 ) {
10631093 let epoch_fee = Fee { denominator, numerator } ;
10641094 let mut stake_pool = StakePool {
1065- total_stake_lamports ,
1066- pool_token_supply: total_stake_lamports ,
1095+ total_lamports ,
1096+ pool_token_supply: total_lamports ,
10671097 epoch_fee,
10681098 ..StakePool :: default ( )
10691099 } ;
10701100 let pool_token_fee = stake_pool. calc_epoch_fee_amount( reward_lamports) . unwrap( ) ;
10711101
1072- stake_pool. total_stake_lamports += reward_lamports;
1102+ stake_pool. total_lamports += reward_lamports;
10731103 stake_pool. pool_token_supply += pool_token_fee;
10741104
10751105 let fee_lamports = stake_pool. calc_lamports_withdraw_amount( pool_token_fee) . unwrap( ) ;
@@ -1082,7 +1112,7 @@ mod test {
10821112 // since we do two "flooring" conversions, the max epsilon should be
10831113 // correct up to 2 lamports (one for each floor division), plus a
10841114 // correction for huge discrepancies between rewards and total stake
1085- let epsilon = 2 + reward_lamports / total_stake_lamports ;
1115+ let epsilon = 2 + reward_lamports / total_lamports ;
10861116 assert!( max_fee_lamports - fee_lamports <= epsilon,
10871117 "Max expected fee in lamports {}, actually receive {}, epsilon {}" ,
10881118 max_fee_lamports, fee_lamports, epsilon) ;
@@ -1102,16 +1132,16 @@ mod test {
11021132 proptest ! {
11031133 #[ test]
11041134 fn deposit_and_withdraw(
1105- ( total_stake_lamports , pool_token_supply, deposit_stake) in total_tokens_and_deposit( )
1135+ ( total_lamports , pool_token_supply, deposit_stake) in total_tokens_and_deposit( )
11061136 ) {
11071137 let mut stake_pool = StakePool {
1108- total_stake_lamports ,
1138+ total_lamports ,
11091139 pool_token_supply,
11101140 ..StakePool :: default ( )
11111141 } ;
11121142 let deposit_result = stake_pool. calc_pool_tokens_for_deposit( deposit_stake) . unwrap( ) ;
11131143 prop_assume!( deposit_result > 0 ) ;
1114- stake_pool. total_stake_lamports += deposit_stake;
1144+ stake_pool. total_lamports += deposit_stake;
11151145 stake_pool. pool_token_supply += deposit_result;
11161146 let withdraw_result = stake_pool. calc_lamports_withdraw_amount( deposit_result) . unwrap( ) ;
11171147 assert!( withdraw_result <= deposit_stake) ;
0 commit comments