@@ -86,12 +86,12 @@ pub struct StakePool {
86
86
/// Total stake under management.
87
87
/// Note that if `last_update_epoch` does not match the current epoch then
88
88
/// this field may not be accurate
89
- pub total_stake_lamports : u64 ,
89
+ pub total_lamports : u64 ,
90
90
91
91
/// Total supply of pool tokens (should always match the supply in the Pool Mint)
92
92
pub pool_token_supply : u64 ,
93
93
94
- /// Last epoch the `total_stake_lamports ` field was updated
94
+ /// Last epoch the `total_lamports ` field was updated
95
95
pub last_update_epoch : u64 ,
96
96
97
97
/// Lockup that all stakes in the pool must have
@@ -146,18 +146,24 @@ pub struct StakePool {
146
146
147
147
/// Future SOL withdrawal fee, to be set for the following epoch
148
148
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 ,
149
155
}
150
156
impl StakePool {
151
157
/// calculate the pool tokens that should be minted for a deposit of `stake_lamports`
152
158
#[ inline]
153
159
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 {
155
161
return Some ( stake_lamports) ;
156
162
}
157
163
u64:: try_from (
158
164
( stake_lamports as u128 )
159
165
. 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 ) ?,
161
167
)
162
168
. ok ( )
163
169
}
@@ -168,7 +174,7 @@ impl StakePool {
168
174
// `checked_ceil_div` returns `None` for a 0 quotient result, but in this
169
175
// case, a return of 0 is valid for small amounts of pool tokens. So
170
176
// 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 ) ?;
172
178
let denominator = self . pool_token_supply as u128 ;
173
179
if numerator < denominator || denominator == 0 {
174
180
Some ( 0 )
@@ -227,22 +233,21 @@ impl StakePool {
227
233
/// Calculate the fee in pool tokens that goes to the manager
228
234
///
229
235
/// 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 `
231
237
#[ inline]
232
238
pub fn calc_epoch_fee_amount ( & self , reward_lamports : u64 ) -> Option < u64 > {
233
239
if reward_lamports == 0 {
234
240
return Some ( 0 ) ;
235
241
}
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 ) ?;
238
243
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 {
240
245
Some ( reward_lamports)
241
246
} else {
242
247
u64:: try_from (
243
248
( self . pool_token_supply as u128 )
244
249
. checked_mul ( fee_lamports) ?
245
- . checked_div ( total_stake_lamports . checked_sub ( fee_lamports) ?) ?,
250
+ . checked_div ( total_lamports . checked_sub ( fee_lamports) ?) ?,
246
251
)
247
252
. ok ( )
248
253
}
@@ -816,7 +821,10 @@ mod test {
816
821
solana_program:: borsh:: {
817
822
get_instance_packed_len, get_packed_len, try_from_slice_unchecked,
818
823
} ,
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
+ } ,
820
828
} ;
821
829
822
830
fn uninitialized_validator_list ( ) -> ValidatorList {
@@ -992,11 +1000,11 @@ mod test {
992
1000
}
993
1001
994
1002
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 ,
998
1006
) -> ( u64 , u64 ) {
999
- ( total_stake_lamports - rewards, rewards)
1007
+ ( total_lamports - rewards, rewards)
1000
1008
}
1001
1009
}
1002
1010
@@ -1008,15 +1016,15 @@ mod test {
1008
1016
denominator : 10 ,
1009
1017
} ;
1010
1018
let mut stake_pool = StakePool {
1011
- total_stake_lamports : 100 * LAMPORTS_PER_SOL ,
1019
+ total_lamports : 100 * LAMPORTS_PER_SOL ,
1012
1020
pool_token_supply : 100 * LAMPORTS_PER_SOL ,
1013
1021
epoch_fee,
1014
1022
..StakePool :: default ( )
1015
1023
} ;
1016
1024
let reward_lamports = 10 * LAMPORTS_PER_SOL ;
1017
1025
let pool_token_fee = stake_pool. calc_epoch_fee_amount ( reward_lamports) . unwrap ( ) ;
1018
1026
1019
- stake_pool. total_stake_lamports += reward_lamports;
1027
+ stake_pool. total_lamports += reward_lamports;
1020
1028
stake_pool. pool_token_supply += pool_token_fee;
1021
1029
1022
1030
let fee_lamports = stake_pool
@@ -1042,7 +1050,7 @@ mod test {
1042
1050
#[ test]
1043
1051
fn divide_by_zero_fee ( ) {
1044
1052
let stake_pool = StakePool {
1045
- total_stake_lamports : 0 ,
1053
+ total_lamports : 0 ,
1046
1054
epoch_fee : Fee {
1047
1055
numerator : 1 ,
1048
1056
denominator : 10 ,
@@ -1054,22 +1062,44 @@ mod test {
1054
1062
assert_eq ! ( fee, rewards) ;
1055
1063
}
1056
1064
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
+
1057
1087
proptest ! {
1058
1088
#[ test]
1059
1089
fn fee_calculation(
1060
1090
( 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( ) ,
1062
1092
) {
1063
1093
let epoch_fee = Fee { denominator, numerator } ;
1064
1094
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 ,
1067
1097
epoch_fee,
1068
1098
..StakePool :: default ( )
1069
1099
} ;
1070
1100
let pool_token_fee = stake_pool. calc_epoch_fee_amount( reward_lamports) . unwrap( ) ;
1071
1101
1072
- stake_pool. total_stake_lamports += reward_lamports;
1102
+ stake_pool. total_lamports += reward_lamports;
1073
1103
stake_pool. pool_token_supply += pool_token_fee;
1074
1104
1075
1105
let fee_lamports = stake_pool. calc_lamports_withdraw_amount( pool_token_fee) . unwrap( ) ;
@@ -1082,7 +1112,7 @@ mod test {
1082
1112
// since we do two "flooring" conversions, the max epsilon should be
1083
1113
// correct up to 2 lamports (one for each floor division), plus a
1084
1114
// 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 ;
1086
1116
assert!( max_fee_lamports - fee_lamports <= epsilon,
1087
1117
"Max expected fee in lamports {}, actually receive {}, epsilon {}" ,
1088
1118
max_fee_lamports, fee_lamports, epsilon) ;
@@ -1102,16 +1132,16 @@ mod test {
1102
1132
proptest ! {
1103
1133
#[ test]
1104
1134
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( )
1106
1136
) {
1107
1137
let mut stake_pool = StakePool {
1108
- total_stake_lamports ,
1138
+ total_lamports ,
1109
1139
pool_token_supply,
1110
1140
..StakePool :: default ( )
1111
1141
} ;
1112
1142
let deposit_result = stake_pool. calc_pool_tokens_for_deposit( deposit_stake) . unwrap( ) ;
1113
1143
prop_assume!( deposit_result > 0 ) ;
1114
- stake_pool. total_stake_lamports += deposit_stake;
1144
+ stake_pool. total_lamports += deposit_stake;
1115
1145
stake_pool. pool_token_supply += deposit_result;
1116
1146
let withdraw_result = stake_pool. calc_lamports_withdraw_amount( deposit_result) . unwrap( ) ;
1117
1147
assert!( withdraw_result <= deposit_stake) ;
0 commit comments