max_swap: Fix swapping between zero weight tokens (#699)
Co-authored-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
03378bb808
commit
c07978fb68
|
@ -197,7 +197,7 @@ impl HealthCache {
|
|||
}
|
||||
|
||||
let amount = {
|
||||
// Now max_value is bigger than min_fn_value, the target amount must be >amount_for_max_value.
|
||||
// Now max_value is bigger than min_fn_value, the target amount must be >=amount_for_max_value.
|
||||
// Search to the right of amount_for_max_value: but how far?
|
||||
// Use a simple estimation for the amount that would lead to zero health:
|
||||
// health
|
||||
|
@ -209,7 +209,10 @@ impl HealthCache {
|
|||
let health_at_max_value = cache_after_swap(amount_for_max_value)?
|
||||
.map(|c| c.health(health_type))
|
||||
.unwrap_or(I80F48::MIN);
|
||||
if health_at_max_value <= 0 {
|
||||
if health_at_max_value == 0 {
|
||||
return Ok(amount_for_max_value);
|
||||
} else if health_at_max_value < 0 {
|
||||
// target_fn suggests health is good but health suggests it's not
|
||||
return Ok(I80F48::ZERO);
|
||||
}
|
||||
let zero_health_estimate =
|
||||
|
@ -506,7 +509,11 @@ fn binary_search(
|
|||
Err(error_msg!("binary search iterations exhausted"))
|
||||
}
|
||||
|
||||
/// This is not a generic function. It assumes there is a unique maximum between left and right.
|
||||
/// This is not a generic function. It assumes there is a almost-unique maximum between left and right,
|
||||
/// in the sense that `fun` might be constant on the maximum value for a while, but there won't be
|
||||
/// distinct maximums with non-maximal values between them.
|
||||
///
|
||||
/// If the maximum isn't just a single point, it returns the rightmost value.
|
||||
fn find_maximum(
|
||||
mut left: I80F48,
|
||||
mut right: I80F48,
|
||||
|
@ -520,7 +527,7 @@ fn find_maximum(
|
|||
let mut right_value = fun(right)?;
|
||||
let mut mid_value = fun(mid)?;
|
||||
while (right - left) > min_step {
|
||||
if left_value >= mid_value {
|
||||
if left_value > mid_value {
|
||||
// max must be between left and mid
|
||||
assert!(mid_value >= right_value);
|
||||
right = mid;
|
||||
|
@ -539,7 +546,7 @@ fn find_maximum(
|
|||
let leftmid = half * (left + mid);
|
||||
let leftmid_value = fun(leftmid)?;
|
||||
assert!(leftmid_value >= left_value);
|
||||
if leftmid_value >= mid_value {
|
||||
if leftmid_value > mid_value {
|
||||
// max between left and mid
|
||||
right = mid;
|
||||
right_value = mid_value;
|
||||
|
@ -568,9 +575,9 @@ fn find_maximum(
|
|||
}
|
||||
}
|
||||
|
||||
if left_value >= mid_value {
|
||||
if left_value > mid_value {
|
||||
Ok((left, left_value))
|
||||
} else if mid_value >= right_value {
|
||||
} else if mid_value > right_value {
|
||||
Ok((mid, mid_value))
|
||||
} else {
|
||||
Ok((right, right_value))
|
||||
|
@ -722,18 +729,28 @@ mod tests {
|
|||
price_factor: f64,
|
||||
banks: [Bank; 3],
|
||||
max_swap_fn: MaxSwapFn| {
|
||||
let source_price = &c.token_infos[source as usize].prices;
|
||||
let source_bank = &banks[source as usize];
|
||||
let target_price = &c.token_infos[target as usize].prices;
|
||||
let target_bank = &banks[target as usize];
|
||||
let source_ti = &c.token_infos[source as usize];
|
||||
let source_price = &source_ti.prices;
|
||||
let mut source_bank = banks[source as usize].clone();
|
||||
// Update the bank weights, because the tests like to modify the cache
|
||||
// weights and expect them to stick
|
||||
source_bank.init_asset_weight = source_ti.init_asset_weight;
|
||||
source_bank.init_liab_weight = source_ti.init_liab_weight;
|
||||
|
||||
let target_ti = &c.token_infos[target as usize];
|
||||
let target_price = &target_ti.prices;
|
||||
let mut target_bank = banks[target as usize].clone();
|
||||
target_bank.init_asset_weight = target_ti.init_asset_weight;
|
||||
target_bank.init_liab_weight = target_ti.init_liab_weight;
|
||||
|
||||
let swap_price =
|
||||
I80F48::from_num(price_factor) * source_price.oracle / target_price.oracle;
|
||||
let source_amount = c
|
||||
.max_swap_source_for_health_fn(
|
||||
&account,
|
||||
source_bank,
|
||||
&source_bank,
|
||||
source_price.oracle,
|
||||
target_bank,
|
||||
&target_bank,
|
||||
swap_price,
|
||||
I80F48::from_num(min_value),
|
||||
max_swap_fn,
|
||||
|
@ -745,9 +762,9 @@ mod tests {
|
|||
let value_for_amount = |amount| {
|
||||
c.cache_after_swap(
|
||||
&account,
|
||||
source_bank,
|
||||
&source_bank,
|
||||
source_price.oracle,
|
||||
target_bank,
|
||||
&target_bank,
|
||||
I80F48::from(amount),
|
||||
swap_price,
|
||||
)
|
||||
|
@ -1033,6 +1050,42 @@ mod tests {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// swap some assets between zero-asset-weight tokens
|
||||
println!("test 11 {test_name}");
|
||||
let mut health_cache = health_cache.clone();
|
||||
adjust_by_usdc(&mut health_cache, 0, 10.0); // 5 tokens
|
||||
health_cache.token_infos[0].init_asset_weight = I80F48::from(0);
|
||||
health_cache.token_infos[1].init_asset_weight = I80F48::from(0);
|
||||
|
||||
let amount = find_max_swap(&health_cache, 0, 1, 1.0, 1.0, banks).0;
|
||||
assert_eq!(amount, 5.0);
|
||||
|
||||
for price_factor in [0.9, 1.1] {
|
||||
for target in 1..100 {
|
||||
let target = target as f64;
|
||||
|
||||
// Result is always the same: swap all deposits
|
||||
let amount =
|
||||
find_max_swap(&health_cache, 0, 1, target, price_factor, banks).0;
|
||||
assert_eq!(amount, 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
adjust_by_usdc(&mut health_cache, 1, 6.0); // 2 tokens
|
||||
|
||||
for price_factor in [0.9, 1.1] {
|
||||
for target in 1..100 {
|
||||
let target = target as f64;
|
||||
|
||||
// Result is always the same: swap all deposits
|
||||
let amount =
|
||||
find_max_swap(&health_cache, 0, 1, target, price_factor, banks).0;
|
||||
assert_eq!(amount, 5.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -717,7 +717,11 @@ export class HealthCache {
|
|||
throw new Error('Could not find amount that led to health ratio <=0');
|
||||
}
|
||||
|
||||
/// This is not a generic function. It assumes there is a unique maximum between left and right.
|
||||
/// This is not a generic function. It assumes there is a almost-unique maximum between left and right,
|
||||
/// in the sense that `fun` might be constant on the maximum value for a while, but there won't be
|
||||
/// distinct maximums with non-maximal values between them.
|
||||
///
|
||||
/// If the maximum isn't just a single point, it returns the rightmost value.
|
||||
private static findMaximum(
|
||||
left: I80F48,
|
||||
right: I80F48,
|
||||
|
@ -730,7 +734,7 @@ export class HealthCache {
|
|||
let rightValue = fun(right);
|
||||
let midValue = fun(mid);
|
||||
while (right.sub(left).gt(minStep)) {
|
||||
if (leftValue.gte(midValue)) {
|
||||
if (leftValue.gt(midValue)) {
|
||||
// max must be between left and mid
|
||||
right = mid;
|
||||
rightValue = midValue;
|
||||
|
@ -746,7 +750,7 @@ export class HealthCache {
|
|||
// mid is larger than both left and right, max could be on either side
|
||||
const leftmid = half.mul(left.add(mid));
|
||||
const leftMidValue = fun(leftmid);
|
||||
if (leftMidValue.gte(midValue)) {
|
||||
if (leftMidValue.gt(midValue)) {
|
||||
// max between left and mid
|
||||
right = mid;
|
||||
rightValue = midValue;
|
||||
|
@ -774,9 +778,9 @@ export class HealthCache {
|
|||
}
|
||||
}
|
||||
|
||||
if (leftValue.gte(midValue)) {
|
||||
if (leftValue.gt(midValue)) {
|
||||
return [left, leftValue];
|
||||
} else if (midValue.gte(rightValue)) {
|
||||
} else if (midValue.gt(rightValue)) {
|
||||
return [mid, midValue];
|
||||
} else {
|
||||
return [right, rightValue];
|
||||
|
@ -1018,7 +1022,9 @@ export class HealthCache {
|
|||
const healthAtMaxValue = cacheAfterSwap(amountForMaxValue).health(
|
||||
HealthType.init,
|
||||
);
|
||||
if (healthAtMaxValue.lte(ZERO_I80F48())) {
|
||||
if (healthAtMaxValue.eq(ZERO_I80F48())) {
|
||||
return amountForMaxValue;
|
||||
} else if (healthAtMaxValue.lt(ZERO_I80F48())) {
|
||||
return ZERO_I80F48();
|
||||
}
|
||||
const zeroHealthEstimate = amountForMaxValue.sub(
|
||||
|
|
Loading…
Reference in New Issue