token-swap: Add proptest for deposit draining (#959)
* Add deposit drain proptest * Add special logic for constant price curve normalized value when close to u128 * Cargo fmt * Fix bpf build
This commit is contained in:
parent
d0d2a22545
commit
942bf77c90
|
@ -740,19 +740,6 @@ dependencies = [
|
||||||
"syn 1.0.48",
|
"syn 1.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "curve25519-dalek"
|
|
||||||
version = "2.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"digest 0.8.1",
|
|
||||||
"rand_core",
|
|
||||||
"subtle 2.2.3",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "curve25519-dalek"
|
name = "curve25519-dalek"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -767,6 +754,19 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"digest 0.8.1",
|
||||||
|
"rand_core",
|
||||||
|
"subtle 2.2.3",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "curve25519-dalek"
|
name = "curve25519-dalek"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
|
|
|
@ -203,6 +203,7 @@ pub trait CurveCalculator: Debug + DynPack {
|
||||||
#[cfg(any(test, fuzzing))]
|
#[cfg(any(test, fuzzing))]
|
||||||
pub mod test {
|
pub mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::curve::math::U256;
|
||||||
|
|
||||||
/// The epsilon for most curves when performing the conversion test,
|
/// The epsilon for most curves when performing the conversion test,
|
||||||
/// comparing a one-sided deposit to a swap + deposit.
|
/// comparing a one-sided deposit to a swap + deposit.
|
||||||
|
@ -348,4 +349,54 @@ pub mod test {
|
||||||
let difference = new_value - previous_value;
|
let difference = new_value - previous_value;
|
||||||
assert!(difference <= epsilon);
|
assert!(difference <= epsilon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test function checking that a deposit never reduces the value of pool
|
||||||
|
/// tokens.
|
||||||
|
///
|
||||||
|
/// Since curve calculations use unsigned integers, there is potential for
|
||||||
|
/// truncation at some point, meaning a potential for value to be lost if
|
||||||
|
/// too much is given to the depositor.
|
||||||
|
pub fn check_pool_value_from_deposit(
|
||||||
|
curve: &dyn CurveCalculator,
|
||||||
|
pool_token_amount: u128,
|
||||||
|
pool_token_supply: u128,
|
||||||
|
swap_token_a_amount: u128,
|
||||||
|
swap_token_b_amount: u128,
|
||||||
|
) {
|
||||||
|
let deposit_result = curve
|
||||||
|
.pool_tokens_to_trading_tokens(
|
||||||
|
pool_token_amount,
|
||||||
|
pool_token_supply,
|
||||||
|
swap_token_a_amount,
|
||||||
|
swap_token_b_amount,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let new_swap_token_a_amount = swap_token_a_amount + deposit_result.token_a_amount;
|
||||||
|
let new_swap_token_b_amount = swap_token_b_amount + deposit_result.token_b_amount;
|
||||||
|
let new_pool_token_supply = pool_token_supply + pool_token_amount;
|
||||||
|
|
||||||
|
// the following inequality must hold:
|
||||||
|
// new_token_a / new_pool_token_supply >= token_a / pool_token_supply
|
||||||
|
// which reduces to:
|
||||||
|
// new_token_a * pool_token_supply >= token_a * new_pool_token_supply
|
||||||
|
|
||||||
|
// These numbers can be just slightly above u64 after the deposit, which
|
||||||
|
// means that their multiplication can be just above the range of u128.
|
||||||
|
// For ease of testing, we bump these up to U256.
|
||||||
|
let pool_token_supply = U256::from(pool_token_supply);
|
||||||
|
let new_pool_token_supply = U256::from(new_pool_token_supply);
|
||||||
|
let swap_token_a_amount = U256::from(swap_token_a_amount);
|
||||||
|
let new_swap_token_a_amount = U256::from(new_swap_token_a_amount);
|
||||||
|
let swap_token_b_amount = U256::from(swap_token_b_amount);
|
||||||
|
let new_swap_token_b_amount = U256::from(new_swap_token_b_amount);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
new_swap_token_a_amount * pool_token_supply
|
||||||
|
>= swap_token_a_amount * new_pool_token_supply
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
new_swap_token_b_amount * pool_token_supply
|
||||||
|
>= swap_token_b_amount * new_pool_token_supply
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,21 +68,13 @@ impl CurveCalculator for ConstantPriceCurve {
|
||||||
swap_token_a_amount: u128,
|
swap_token_a_amount: u128,
|
||||||
swap_token_b_amount: u128,
|
swap_token_b_amount: u128,
|
||||||
) -> Option<TradingTokenResult> {
|
) -> Option<TradingTokenResult> {
|
||||||
// Split the pool tokens in half, send half as token A, half as token B
|
|
||||||
let token_a_pool_tokens = pool_tokens.checked_div(2)?;
|
|
||||||
let token_b_pool_tokens = pool_tokens.checked_sub(token_a_pool_tokens)?;
|
|
||||||
|
|
||||||
let token_b_price = self.token_b_price as u128;
|
let token_b_price = self.token_b_price as u128;
|
||||||
let total_value = swap_token_b_amount
|
let total_value = self.normalized_value(swap_token_a_amount, swap_token_b_amount)?;
|
||||||
.checked_mul(token_b_price)?
|
|
||||||
.checked_add(swap_token_a_amount)?;
|
|
||||||
|
|
||||||
let (token_a_amount, _) = ceiling_division(
|
let (token_a_amount, _) =
|
||||||
token_a_pool_tokens.checked_mul(total_value)?,
|
ceiling_division(pool_tokens.checked_mul(total_value)?, pool_token_supply)?;
|
||||||
pool_token_supply,
|
|
||||||
)?;
|
|
||||||
let (token_b_amount, _) = ceiling_division(
|
let (token_b_amount, _) = ceiling_division(
|
||||||
token_b_pool_tokens
|
pool_tokens
|
||||||
.checked_mul(total_value)?
|
.checked_mul(total_value)?
|
||||||
.checked_div(token_b_price)?,
|
.checked_div(token_b_price)?,
|
||||||
pool_token_supply,
|
pool_token_supply,
|
||||||
|
@ -142,13 +134,27 @@ impl CurveCalculator for ConstantPriceCurve {
|
||||||
/// Note that since most other curves use a multiplicative invariant, ie.
|
/// Note that since most other curves use a multiplicative invariant, ie.
|
||||||
/// `token_a * token_b`, whereas this one uses an addition,
|
/// `token_a * token_b`, whereas this one uses an addition,
|
||||||
/// ie. `token_a + token_b`.
|
/// ie. `token_a + token_b`.
|
||||||
|
///
|
||||||
|
/// At the end, we divide by 2 to normalize the value between the two token
|
||||||
|
/// types.
|
||||||
fn normalized_value(
|
fn normalized_value(
|
||||||
&self,
|
&self,
|
||||||
swap_token_a_amount: u128,
|
swap_token_a_amount: u128,
|
||||||
swap_token_b_amount: u128,
|
swap_token_b_amount: u128,
|
||||||
) -> Option<u128> {
|
) -> Option<u128> {
|
||||||
let swap_token_b_amount = swap_token_b_amount.checked_mul(self.token_b_price as u128)?;
|
let swap_token_b_value = swap_token_b_amount.checked_mul(self.token_b_price as u128)?;
|
||||||
swap_token_a_amount.checked_add(swap_token_b_amount)
|
// special logic in case we're close to the limits, avoid overflowing u128
|
||||||
|
if swap_token_b_value.saturating_sub(std::u64::MAX.into())
|
||||||
|
> (std::u128::MAX.saturating_sub(std::u64::MAX.into()))
|
||||||
|
{
|
||||||
|
swap_token_b_value
|
||||||
|
.checked_div(2)?
|
||||||
|
.checked_add(swap_token_a_amount.checked_div(2)?)
|
||||||
|
} else {
|
||||||
|
swap_token_a_amount
|
||||||
|
.checked_add(swap_token_b_value)?
|
||||||
|
.checked_div(2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,4 +427,55 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn curve_value_does_not_decrease_from_deposit(
|
||||||
|
pool_token_amount in 2..u64::MAX, // minimum 2 to splitting on deposit
|
||||||
|
pool_token_supply in INITIAL_SWAP_POOL_AMOUNT..u64::MAX as u128,
|
||||||
|
swap_token_a_amount in 1..u64::MAX,
|
||||||
|
swap_token_b_amount in 1..u32::MAX, // kept small to avoid proptest rejections
|
||||||
|
token_b_price in 1..u32::MAX, // kept small to avoid proptest rejections
|
||||||
|
) {
|
||||||
|
let curve = ConstantPriceCurve { token_b_price: token_b_price as u64 };
|
||||||
|
let pool_token_amount = pool_token_amount as u128;
|
||||||
|
let pool_token_supply = pool_token_supply as u128;
|
||||||
|
let swap_token_a_amount = swap_token_a_amount as u128;
|
||||||
|
let swap_token_b_amount = swap_token_b_amount as u128;
|
||||||
|
let token_b_price = token_b_price as u128;
|
||||||
|
|
||||||
|
let value = curve.normalized_value(swap_token_a_amount, swap_token_b_amount).unwrap();
|
||||||
|
|
||||||
|
// Make sure we trade at least one of each token
|
||||||
|
prop_assume!(pool_token_amount * value >= 2 * token_b_price * pool_token_supply);
|
||||||
|
let deposit_result = curve
|
||||||
|
.pool_tokens_to_trading_tokens(
|
||||||
|
pool_token_amount,
|
||||||
|
pool_token_supply,
|
||||||
|
swap_token_a_amount,
|
||||||
|
swap_token_b_amount,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let new_swap_token_a_amount = swap_token_a_amount + deposit_result.token_a_amount;
|
||||||
|
let new_swap_token_b_amount = swap_token_b_amount + deposit_result.token_b_amount;
|
||||||
|
let new_pool_token_supply = pool_token_supply + pool_token_amount;
|
||||||
|
|
||||||
|
let new_value = curve.normalized_value(new_swap_token_a_amount, new_swap_token_b_amount).unwrap();
|
||||||
|
|
||||||
|
// the following inequality must hold:
|
||||||
|
// new_value / new_pool_token_supply >= value / pool_token_supply
|
||||||
|
// which reduces to:
|
||||||
|
// new_value * pool_token_supply >= value * new_pool_token_supply
|
||||||
|
|
||||||
|
// These numbers can be just slightly above u64 after the deposit, which
|
||||||
|
// means that their multiplication can be just above the range of u128.
|
||||||
|
// For ease of testing, we bump these up to U256.
|
||||||
|
let pool_token_supply = U256::from(pool_token_supply);
|
||||||
|
let new_pool_token_supply = U256::from(new_pool_token_supply);
|
||||||
|
let value = U256::from(value);
|
||||||
|
let new_value = U256::from(new_value);
|
||||||
|
|
||||||
|
assert!(new_value * pool_token_supply >= value * new_pool_token_supply);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ mod tests {
|
||||||
use crate::curve::calculator::{
|
use crate::curve::calculator::{
|
||||||
test::{
|
test::{
|
||||||
check_curve_value_from_swap, check_pool_token_conversion,
|
check_curve_value_from_swap, check_pool_token_conversion,
|
||||||
CONVERSION_BASIS_POINTS_GUARANTEE,
|
check_pool_value_from_deposit, CONVERSION_BASIS_POINTS_GUARANTEE,
|
||||||
},
|
},
|
||||||
INITIAL_SWAP_POOL_AMOUNT,
|
INITIAL_SWAP_POOL_AMOUNT,
|
||||||
};
|
};
|
||||||
|
@ -263,4 +263,31 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn curve_value_does_not_decrease_from_deposit(
|
||||||
|
pool_token_amount in 1..u64::MAX,
|
||||||
|
pool_token_supply in 1..u64::MAX,
|
||||||
|
swap_token_a_amount in 1..u64::MAX,
|
||||||
|
swap_token_b_amount in 1..u64::MAX,
|
||||||
|
) {
|
||||||
|
let pool_token_amount = pool_token_amount as u128;
|
||||||
|
let pool_token_supply = pool_token_supply as u128;
|
||||||
|
let swap_token_a_amount = swap_token_a_amount as u128;
|
||||||
|
let swap_token_b_amount = swap_token_b_amount as u128;
|
||||||
|
// Make sure we will get at least one trading token out for each
|
||||||
|
// side, otherwise the calculation fails
|
||||||
|
prop_assume!(pool_token_amount * swap_token_a_amount / pool_token_supply >= 1);
|
||||||
|
prop_assume!(pool_token_amount * swap_token_b_amount / pool_token_supply >= 1);
|
||||||
|
let curve = ConstantProductCurve {};
|
||||||
|
check_pool_value_from_deposit(
|
||||||
|
&curve,
|
||||||
|
pool_token_amount,
|
||||||
|
pool_token_supply,
|
||||||
|
swap_token_a_amount,
|
||||||
|
swap_token_b_amount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -174,7 +174,7 @@ mod tests {
|
||||||
use crate::curve::calculator::{
|
use crate::curve::calculator::{
|
||||||
test::{
|
test::{
|
||||||
check_curve_value_from_swap, check_pool_token_conversion,
|
check_curve_value_from_swap, check_pool_token_conversion,
|
||||||
CONVERSION_BASIS_POINTS_GUARANTEE,
|
check_pool_value_from_deposit, CONVERSION_BASIS_POINTS_GUARANTEE,
|
||||||
},
|
},
|
||||||
INITIAL_SWAP_POOL_AMOUNT,
|
INITIAL_SWAP_POOL_AMOUNT,
|
||||||
};
|
};
|
||||||
|
@ -430,4 +430,37 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn curve_value_does_not_decrease_from_deposit(
|
||||||
|
pool_token_amount in 1..u64::MAX,
|
||||||
|
pool_token_supply in 1..u64::MAX,
|
||||||
|
swap_token_a_amount in 1..u64::MAX,
|
||||||
|
swap_token_b_amount in 1..u64::MAX,
|
||||||
|
token_b_offset in 1..u64::MAX,
|
||||||
|
) {
|
||||||
|
let curve = OffsetCurve { token_b_offset };
|
||||||
|
let pool_token_amount = pool_token_amount as u128;
|
||||||
|
let pool_token_supply = pool_token_supply as u128;
|
||||||
|
let swap_token_a_amount = swap_token_a_amount as u128;
|
||||||
|
let swap_token_b_amount = swap_token_b_amount as u128;
|
||||||
|
let token_b_offset = token_b_offset as u128;
|
||||||
|
|
||||||
|
// For ease of calc, make sure the token B side stays within u64
|
||||||
|
prop_assume!(token_b_offset + swap_token_b_amount <= u64::MAX.into());
|
||||||
|
|
||||||
|
// Make sure we will get at least one trading token out for each
|
||||||
|
// side, otherwise the calculation fails
|
||||||
|
prop_assume!(pool_token_amount * swap_token_a_amount / pool_token_supply >= 1);
|
||||||
|
prop_assume!(pool_token_amount * (swap_token_b_amount + token_b_offset) / pool_token_supply >= 1);
|
||||||
|
check_pool_value_from_deposit(
|
||||||
|
&curve,
|
||||||
|
pool_token_amount,
|
||||||
|
pool_token_supply,
|
||||||
|
swap_token_a_amount,
|
||||||
|
swap_token_b_amount,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue