stake-pool: Use checked_ceil_div for withdraw calc (#1482)

* stake-pool: Use checked_ceil_div for withdraw calc

When a stake account is totally removed from a stake pool by the
manager, there's a chance that the operation would not take enough of
the manager's pool tokens by 1 due to truncation.

Do a ceiling division instead, and refactor ceiling division into the
math library.

* Use new function name on CLI

* Cargo fmt
This commit is contained in:
Jon Cinque 2021-03-23 00:33:51 +01:00 committed by GitHub
parent 04fc247b2e
commit d336b8b714
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 32 additions and 26 deletions

1
Cargo.lock generated
View File

@ -3865,6 +3865,7 @@ dependencies = [
"solana-program-test",
"solana-sdk",
"solana-vote-program",
"spl-math",
"spl-token 3.1.0",
"thiserror",
]

View File

@ -1,6 +1,6 @@
//! Defines useful math utils
//! Defines performing checked ceiling division for different types
use spl_math::uint::U256;
use crate::uint::U256;
/// Perform a division that does not truncate value from either side, returning
/// the (quotient, divisor) as a tuple

View File

@ -4,6 +4,7 @@
#![forbid(unsafe_code)]
pub mod approximations;
pub mod checked_ceil_div;
mod entrypoint;
pub mod error;
pub mod instruction;

View File

@ -777,7 +777,7 @@ fn pick_withdraw_accounts(
continue;
}
let available_for_withdrawal = stake_pool
.calc_lamports_amount(account.lamports - *MIN_STAKE_BALANCE)
.calc_lamports_withdraw_amount(account.lamports - *MIN_STAKE_BALANCE)
.unwrap();
let withdraw_amount = u64::min(available_for_withdrawal, remaining_amount);
@ -878,7 +878,7 @@ fn command_withdraw(
for withdraw_stake in withdraw_accounts {
// Convert pool tokens amount to lamports
let sol_withdraw_amount = pool_data
.calc_lamports_amount(withdraw_stake.pool_amount)
.calc_lamports_withdraw_amount(withdraw_stake.pool_amount)
.unwrap();
println!(

View File

@ -19,6 +19,7 @@ num_enum = "0.5.1"
serde = "1.0.121"
serde_derive = "1.0.103"
solana-program = "1.6.1"
spl-math = { path = "../../libraries/math", features = [ "no-entrypoint" ] }
spl-token = { path = "../../token/program", features = [ "no-entrypoint" ] }
thiserror = "1.0"
bincode = "1.3.1"

View File

@ -1090,7 +1090,7 @@ impl Processor {
.ok_or(StakePoolError::ValidatorNotFound)?;
let stake_amount = stake_pool
.calc_lamports_amount(pool_amount)
.calc_lamports_withdraw_amount(pool_amount)
.ok_or(StakePoolError::CalculationFailure)?;
Self::stake_split(

View File

@ -6,6 +6,7 @@ use {
account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError,
pubkey::Pubkey,
},
spl_math::checked_ceil_div::CheckedCeilDiv,
std::convert::{TryFrom, TryInto},
std::mem::size_of,
};
@ -50,10 +51,6 @@ impl StakePool {
if self.stake_total == 0 {
return Some(stake_lamports);
}
self.calc_pool_withdraw_amount(stake_lamports)
}
/// calculate the pool tokens that should be withdrawn
pub fn calc_pool_withdraw_amount(&self, stake_lamports: u64) -> Option<u64> {
u64::try_from(
(stake_lamports as u128)
.checked_mul(self.pool_total as u128)?
@ -61,8 +58,15 @@ impl StakePool {
)
.ok()
}
/// calculate lamports amount
pub fn calc_lamports_amount(&self, pool_tokens: u64) -> Option<u64> {
/// calculate the pool tokens that should be withdrawn
pub fn calc_pool_withdraw_amount(&self, stake_lamports: u64) -> Option<u64> {
let (quotient, _) = (stake_lamports as u128)
.checked_mul(self.pool_total as u128)?
.checked_ceil_div(self.stake_total as u128)?;
u64::try_from(quotient).ok()
}
/// calculate lamports amount on withdrawal
pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {
u64::try_from(
(pool_tokens as u128)
.checked_mul(self.stake_total as u128)?

View File

@ -1,19 +1,20 @@
//! Simple constant price swap curve, set at init
use crate::{
curve::calculator::{
map_zero_to_none, CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult,
TradeDirection, TradingTokenResult,
use {
crate::{
curve::calculator::{
map_zero_to_none, CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult,
TradeDirection, TradingTokenResult,
},
error::SwapError,
},
curve::math::CheckedCeilDiv,
error::SwapError,
arrayref::{array_mut_ref, array_ref},
solana_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
},
spl_math::{checked_ceil_div::CheckedCeilDiv, precise_number::PreciseNumber, uint::U256},
};
use arrayref::{array_mut_ref, array_ref};
use solana_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
};
use spl_math::{precise_number::PreciseNumber, uint::U256};
/// ConstantPriceCurve struct implementing CurveCalculator
#[derive(Clone, Debug, Default, PartialEq)]

View File

@ -6,14 +6,13 @@ use {
map_zero_to_none, CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult,
TradeDirection, TradingTokenResult,
},
curve::math::CheckedCeilDiv,
error::SwapError,
},
solana_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
},
spl_math::precise_number::PreciseNumber,
spl_math::{checked_ceil_div::CheckedCeilDiv, precise_number::PreciseNumber},
};
/// ConstantProductCurve struct implementing CurveCalculator

View File

@ -5,6 +5,5 @@ pub mod calculator;
pub mod constant_price;
pub mod constant_product;
pub mod fees;
pub mod math;
pub mod offset;
pub mod stable;