|
18 | 18 | pubkey::{Pubkey, PUBKEY_BYTES},
|
19 | 19 | stake::state::Lockup,
|
20 | 20 | },
|
21 |
| - spl_math::checked_ceil_div::CheckedCeilDiv, |
22 | 21 | spl_token::state::{Account, AccountState},
|
23 | 22 | std::{borrow::Borrow, convert::TryFrom, fmt, matches},
|
24 | 23 | };
|
@@ -172,16 +171,15 @@ impl StakePool {
|
172 | 171 | /// calculate lamports amount on withdrawal
|
173 | 172 | #[inline]
|
174 | 173 | pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {
|
175 |
| - // `checked_ceil_div` returns `None` for a 0 quotient result, but in this |
| 174 | + // `checked_div` returns `None` for a 0 quotient result, but in this |
176 | 175 | // case, a return of 0 is valid for small amounts of pool tokens. So
|
177 | 176 | // we check for that separately
|
178 | 177 | let numerator = (pool_tokens as u128).checked_mul(self.total_lamports as u128)?;
|
179 | 178 | let denominator = self.pool_token_supply as u128;
|
180 | 179 | if numerator < denominator || denominator == 0 {
|
181 | 180 | Some(0)
|
182 | 181 | } else {
|
183 |
| - let (quotient, _) = numerator.checked_ceil_div(denominator)?; |
184 |
| - u64::try_from(quotient).ok() |
| 182 | + u64::try_from(numerator.checked_div(denominator)?).ok() |
185 | 183 | }
|
186 | 184 | }
|
187 | 185 |
|
@@ -1033,7 +1031,7 @@ mod test {
|
1033 | 1031 | let fee_lamports = stake_pool
|
1034 | 1032 | .calc_lamports_withdraw_amount(pool_token_fee)
|
1035 | 1033 | .unwrap();
|
1036 |
| - assert_eq!(fee_lamports, LAMPORTS_PER_SOL); |
| 1034 | + assert_eq!(fee_lamports, LAMPORTS_PER_SOL - 1); // off-by-one due to truncation |
1037 | 1035 | }
|
1038 | 1036 |
|
1039 | 1037 | #[test]
|
@@ -1148,6 +1146,80 @@ mod test {
|
1148 | 1146 | stake_pool.pool_token_supply += deposit_result;
|
1149 | 1147 | let withdraw_result = stake_pool.calc_lamports_withdraw_amount(deposit_result).unwrap();
|
1150 | 1148 | assert!(withdraw_result <= deposit_stake);
|
| 1149 | + |
| 1150 | + // also test splitting the withdrawal in two operations |
| 1151 | + if deposit_result >= 2 { |
| 1152 | + let first_half_deposit = deposit_result / 2; |
| 1153 | + let first_withdraw_result = stake_pool.calc_lamports_withdraw_amount(first_half_deposit).unwrap(); |
| 1154 | + stake_pool.total_lamports -= first_withdraw_result; |
| 1155 | + stake_pool.pool_token_supply -= first_half_deposit; |
| 1156 | + let second_half_deposit = deposit_result - first_half_deposit; // do the whole thing |
| 1157 | + let second_withdraw_result = stake_pool.calc_lamports_withdraw_amount(second_half_deposit).unwrap(); |
| 1158 | + assert!(first_withdraw_result + second_withdraw_result <= deposit_stake); |
| 1159 | + } |
1151 | 1160 | }
|
1152 | 1161 | }
|
| 1162 | + |
| 1163 | + #[test] |
| 1164 | + fn specific_split_withdrawal() { |
| 1165 | + let total_lamports = 1_100_000_000_000; |
| 1166 | + let pool_token_supply = 1_000_000_000_000; |
| 1167 | + let deposit_stake = 3; |
| 1168 | + let mut stake_pool = StakePool { |
| 1169 | + total_lamports, |
| 1170 | + pool_token_supply, |
| 1171 | + ..StakePool::default() |
| 1172 | + }; |
| 1173 | + let deposit_result = stake_pool |
| 1174 | + .calc_pool_tokens_for_deposit(deposit_stake) |
| 1175 | + .unwrap(); |
| 1176 | + assert!(deposit_result > 0); |
| 1177 | + stake_pool.total_lamports += deposit_stake; |
| 1178 | + stake_pool.pool_token_supply += deposit_result; |
| 1179 | + let withdraw_result = stake_pool |
| 1180 | + .calc_lamports_withdraw_amount(deposit_result / 2) |
| 1181 | + .unwrap(); |
| 1182 | + assert!(withdraw_result * 2 <= deposit_stake); |
| 1183 | + } |
| 1184 | + |
| 1185 | + #[test] |
| 1186 | + fn withdraw_all() { |
| 1187 | + let total_lamports = 1_100_000_000_000; |
| 1188 | + let pool_token_supply = 1_000_000_000_000; |
| 1189 | + let mut stake_pool = StakePool { |
| 1190 | + total_lamports, |
| 1191 | + pool_token_supply, |
| 1192 | + ..StakePool::default() |
| 1193 | + }; |
| 1194 | + // take everything out at once |
| 1195 | + let withdraw_result = stake_pool |
| 1196 | + .calc_lamports_withdraw_amount(pool_token_supply) |
| 1197 | + .unwrap(); |
| 1198 | + assert_eq!(stake_pool.total_lamports, withdraw_result); |
| 1199 | + |
| 1200 | + // take out 1, then the rest |
| 1201 | + let withdraw_result = stake_pool.calc_lamports_withdraw_amount(1).unwrap(); |
| 1202 | + stake_pool.total_lamports -= withdraw_result; |
| 1203 | + stake_pool.pool_token_supply -= 1; |
| 1204 | + let withdraw_result = stake_pool |
| 1205 | + .calc_lamports_withdraw_amount(stake_pool.pool_token_supply) |
| 1206 | + .unwrap(); |
| 1207 | + assert_eq!(stake_pool.total_lamports, withdraw_result); |
| 1208 | + |
| 1209 | + // take out all except 1, then the rest |
| 1210 | + let mut stake_pool = StakePool { |
| 1211 | + total_lamports, |
| 1212 | + pool_token_supply, |
| 1213 | + ..StakePool::default() |
| 1214 | + }; |
| 1215 | + let withdraw_result = stake_pool |
| 1216 | + .calc_lamports_withdraw_amount(pool_token_supply - 1) |
| 1217 | + .unwrap(); |
| 1218 | + stake_pool.total_lamports -= withdraw_result; |
| 1219 | + stake_pool.pool_token_supply = 1; |
| 1220 | + assert_ne!(stake_pool.total_lamports, 0); |
| 1221 | + |
| 1222 | + let withdraw_result = stake_pool.calc_lamports_withdraw_amount(1).unwrap(); |
| 1223 | + assert_eq!(stake_pool.total_lamports, withdraw_result); |
| 1224 | + } |
1153 | 1225 | }
|
0 commit comments