Skip to content

Commit

Permalink
Merge pull request #360 from invariant-labs/add-checked-cross-tick
Browse files Browse the repository at this point in the history
Add checked cross tick
  • Loading branch information
wojciech-cichocki authored Apr 3, 2024
2 parents e1c5521 + 8f7ab51 commit eb19b52
Showing 1 changed file with 191 additions and 11 deletions.
202 changes: 191 additions & 11 deletions programs/invariant/invariant-types/src/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,14 +494,13 @@ pub fn cross_tick(tick: &mut RefMut<Tick>, pool: &mut Pool) -> Result<()> {
.unchecked_sub(tick.fee_growth_outside_y);

// When going to higher tick net_liquidity should be added and for going lower subtracted
if (pool.current_tick_index >= tick.index) ^ tick.sign {
// trunk-ignore(clippy/assign_op_pattern)
pool.liquidity = pool.liquidity + tick.liquidity_change;
let new_liquidity = if (pool.current_tick_index >= tick.index) ^ tick.sign {
pool.liquidity.checked_add(tick.liquidity_change)
} else {
// trunk-ignore(clippy/assign_op_pattern)
pool.liquidity = pool.liquidity - tick.liquidity_change;
}
pool.liquidity.checked_sub(tick.liquidity_change)
};

pool.liquidity = new_liquidity.map_err(|_| InvariantErrorCode::InvalidPoolLiquidity)?;
Ok(())
}

Expand Down Expand Up @@ -535,21 +534,23 @@ pub fn get_min_sqrt_price(tick_spacing: u16) -> Price {

#[cfg(test)]
mod tests {
use std::cell::RefCell;

use decimal::{BetweenDecimals, BigOps, Decimal, Factories};

use crate::{
decimals::{FixedPoint, Liquidity, Price, TokenAmount},
math::{
compute_swap_step, get_delta_x, get_delta_y, get_max_sqrt_price, get_max_tick,
get_min_sqrt_price, get_min_tick, get_next_sqrt_price_x_up, get_next_sqrt_price_y_down,
SwapResult,
compute_swap_step, cross_tick, get_delta_x, get_delta_y, get_max_sqrt_price,
get_max_tick, get_min_sqrt_price, get_min_tick, get_next_sqrt_price_x_up,
get_next_sqrt_price_y_down, SwapResult,
},
structs::MAX_TICK,
structs::{Pool, Tick, MAX_TICK},
utils::TrackableError,
MAX_SQRT_PRICE, MIN_SQRT_PRICE,
};

use super::{calculate_price_sqrt, is_enough_amount_to_push_price};
use super::{calculate_price_sqrt, is_enough_amount_to_push_price, FeeGrowth};

#[test]
fn test_compute_swap_step() {
Expand Down Expand Up @@ -1780,4 +1781,183 @@ mod tests {
);
}
}

#[test]
fn test_cross_tick() {
// add liquidity to pool
{
let mut pool = Pool {
fee_growth_global_x: FeeGrowth::new(45),
fee_growth_global_y: FeeGrowth::new(35),
liquidity: Liquidity::from_integer(4),
current_tick_index: 7,
..Default::default()
};
let tick = Tick {
fee_growth_outside_x: FeeGrowth::new(30),
fee_growth_outside_y: FeeGrowth::new(25),
index: 3,
liquidity_change: Liquidity::from_integer(1),
..Default::default()
};
let result_pool = Pool {
fee_growth_global_x: FeeGrowth::new(45),
fee_growth_global_y: FeeGrowth::new(35),
liquidity: Liquidity::from_integer(5),
current_tick_index: 7,
..Default::default()
};
let result_tick = Tick {
fee_growth_outside_x: FeeGrowth::new(15),
fee_growth_outside_y: FeeGrowth::new(10),
index: 3,
liquidity_change: Liquidity::from_integer(1),
..Default::default()
};

let ref_tick = RefCell::new(tick);
let mut refmut_tick = ref_tick.borrow_mut();

cross_tick(&mut refmut_tick, &mut pool).unwrap();

assert_eq!(*refmut_tick, result_tick);
assert_eq!(pool, result_pool);
}
{
let mut pool = Pool {
fee_growth_global_x: FeeGrowth::new(68),
fee_growth_global_y: FeeGrowth::new(59),
liquidity: Liquidity::new(0),
current_tick_index: 4,
..Default::default()
};
let tick = Tick {
fee_growth_outside_x: FeeGrowth::new(42),
fee_growth_outside_y: FeeGrowth::new(14),
index: 9,
liquidity_change: Liquidity::new(0),
..Default::default()
};
let result_pool = Pool {
fee_growth_global_x: FeeGrowth::new(68),
fee_growth_global_y: FeeGrowth::new(59),
liquidity: Liquidity::new(0),
current_tick_index: 4,
..Default::default()
};
let result_tick = Tick {
fee_growth_outside_x: FeeGrowth::new(26),
fee_growth_outside_y: FeeGrowth::new(45),
index: 9,
liquidity_change: Liquidity::from_integer(0),
..Default::default()
};

let ref_tick = RefCell::new(tick);
let mut refmut_tick = ref_tick.borrow_mut();
cross_tick(&mut refmut_tick, &mut pool).unwrap();
assert_eq!(*refmut_tick, result_tick);
assert_eq!(pool, result_pool);
}
// fee_growth_outside should underflow
{
let mut pool = Pool {
fee_growth_global_x: FeeGrowth::new(3402),
fee_growth_global_y: FeeGrowth::new(3401),
liquidity: Liquidity::new(14),
current_tick_index: 9,
..Default::default()
};
let tick = Tick {
fee_growth_outside_x: FeeGrowth::new(26584),
fee_growth_outside_y: FeeGrowth::new(1256588),
index: 45,
liquidity_change: Liquidity::new(10),
..Default::default()
};
let result_pool = Pool {
fee_growth_global_x: FeeGrowth::new(3402),
fee_growth_global_y: FeeGrowth::new(3401),
liquidity: Liquidity::new(4),
current_tick_index: 9,
..Default::default()
};
let result_tick = Tick {
fee_growth_outside_x: FeeGrowth::new(340282366920938463463374607431768188274),
fee_growth_outside_y: FeeGrowth::new(340282366920938463463374607431766958269),
index: 45,
liquidity_change: Liquidity::new(10),
..Default::default()
};

let fef_tick = RefCell::new(tick);
let mut refmut_tick = fef_tick.borrow_mut();
cross_tick(&mut refmut_tick, &mut pool).unwrap();
assert_eq!(*refmut_tick, result_tick);
assert_eq!(pool, result_pool);
}
// seconds_per_liquidity_outside should underflow
{
let mut pool = Pool {
fee_growth_global_x: FeeGrowth::new(145),
fee_growth_global_y: FeeGrowth::new(364),
liquidity: Liquidity::new(14),
current_tick_index: 9,
..Default::default()
};
let tick = Tick {
fee_growth_outside_x: FeeGrowth::new(99),
fee_growth_outside_y: FeeGrowth::new(256),
index: 45,
liquidity_change: Liquidity::new(10),
..Default::default()
};
let result_pool = Pool {
fee_growth_global_x: FeeGrowth::new(145),
fee_growth_global_y: FeeGrowth::new(364),
liquidity: Liquidity::new(4),
current_tick_index: 9,
..Default::default()
};
let result_tick = Tick {
fee_growth_outside_x: FeeGrowth::new(46),
fee_growth_outside_y: FeeGrowth::new(108),
index: 45,
liquidity_change: Liquidity::new(10),
..Default::default()
};

let fef_tick = RefCell::new(tick);
let mut refmut_tick = fef_tick.borrow_mut();
cross_tick(&mut refmut_tick, &mut pool).unwrap();
assert_eq!(*refmut_tick, result_tick);
assert_eq!(pool, result_pool);
}
// inconsistent state test cases
{
// underflow of pool.liquidity during cross_tick
{
let mut pool = Pool {
liquidity: Liquidity::from_integer(4),
current_tick_index: 7,
..Default::default()
};
let tick = Tick {
index: 10,
liquidity_change: Liquidity::from_integer(5),
..Default::default()
};
// state of pool and tick be should unchanged
let result_pool = pool.clone();
let result_tick = tick.clone();

let ref_tick = RefCell::new(tick);
let mut refmut_tick = ref_tick.borrow_mut();

cross_tick(&mut refmut_tick, &mut pool).unwrap_err();
assert_eq!(*refmut_tick, result_tick);
assert_eq!(pool, result_pool);
}
}
}
}

0 comments on commit eb19b52

Please sign in to comment.