diff --git a/pkg/seawater/src/host_test_utils.rs b/pkg/seawater/src/host_test_utils.rs index 68c5f947..86e1f3a1 100644 --- a/pkg/seawater/src/host_test_utils.rs +++ b/pkg/seawater/src/host_test_utils.rs @@ -20,6 +20,11 @@ macro_rules! current_test { }; } +pub fn decode_sqrt_price_num(sqrt_price_x96: U256, denom: u64) -> U256 { + let numerator = sqrt_price_x96.pow(U256::from(2)) * U256::from(denom); + numerator >> 192 +} + // encodes a a/b price as a sqrt.q96 price pub fn encode_sqrt_price(num: u64, denom: u64) -> U256 { let num = U256::from(num); diff --git a/pkg/seawater/src/lib.rs b/pkg/seawater/src/lib.rs index 75d37349..2dbcafa9 100644 --- a/pkg/seawater/src/lib.rs +++ b/pkg/seawater/src/lib.rs @@ -286,8 +286,8 @@ impl Pools { } /// Performs a two stage swap, using approvals to transfer tokens. See [Self::swap_2_internal]. - #[allow(non_snake_case)] - pub fn swap_2_exact_in_41203_F1_D( + #[allow(non_snake_case)] + pub fn swap_2_exact_in_41203_F1_D( &mut self, from: Address, to: Address, @@ -359,7 +359,9 @@ impl Pools { // slight hack - we cfg out the whole function, since the `selector` and `raw` attributes don't // actually exist, so we can't `cfg_attr` them in #[cfg(feature = "swap_permit2")] - #[selector(id = "swapPermit2EE84AD91(address,bool,int256,uint256,uint256,uint256,uint256,bytes)")] + #[selector( + id = "swapPermit2EE84AD91(address,bool,int256,uint256,uint256,uint256,uint256,bytes)" + )] #[raw] #[allow(non_snake_case)] pub fn swap_permit_2_E_E84_A_D91(&mut self, data: &[u8]) -> RawArbResult { @@ -395,7 +397,9 @@ impl Pools { /// Performs a two stage swap, using permit2 to transfer tokens. See [Self::swap_2_internal]. #[cfg(feature = "swap_permit2")] - #[selector(id = "swap2ExactInPermit236B2FDD8(address,address,uint256,uint256,uint256,uint256,bytes)")] + #[selector( + id = "swap2ExactInPermit236B2FDD8(address,address,uint256,uint256,uint256,uint256,bytes)" + )] #[raw] #[allow(non_snake_case)] pub fn swap_2_exact_in_permit_2_36_B2_F_D_D8(&mut self, data: &[u8]) -> RawArbResult { @@ -457,7 +461,12 @@ impl Pools { /// # Errors /// Requires the pool to exist and be enabled. #[allow(non_snake_case)] - pub fn mint_position_B_C5_B086_D(&mut self, pool: Address, lower: i32, upper: i32) -> Result { + pub fn mint_position_B_C5_B086_D( + &mut self, + pool: Address, + lower: i32, + upper: i32, + ) -> Result { let id = self.next_position_id.get(); self.pools.setter(pool).create_position(id, lower, upper)?; @@ -485,7 +494,7 @@ impl Pools { /// # Errors /// Requires the position be owned by the caller. Requires the pool to be enabled. #[allow(non_snake_case)] - pub fn burn_position_AE401070(&mut self, id: U256) -> Result<(), Revert> { + pub fn burn_position_AE401070(&mut self, id: U256) -> Result<(), Revert> { let owner = msg::sender(); assert_eq_or!( self.position_owners.get(id), @@ -509,7 +518,7 @@ impl Pools { /// # Errors /// Requires the caller be the NFT manager. #[allow(non_snake_case)] - pub fn transfer_position_E_E_C7_A3_C_D( + pub fn transfer_position_E_E_C7_A3_C_D( &mut self, id: U256, from: Address, @@ -533,13 +542,13 @@ impl Pools { /// Returns the number of positions owned by an address. #[allow(non_snake_case)] - pub fn position_balance_4_F32_C7_D_B(&self, user: Address) -> Result { + pub fn position_balance_4_F32_C7_D_B(&self, user: Address) -> Result { Ok(self.owned_positions.get(user)) } /// Returns the amount of liquidity in a position. #[allow(non_snake_case)] - pub fn position_liquidity_8_D11_C045(&self, pool: Address, id: U256) -> Result { + pub fn position_liquidity_8_D11_C045(&self, pool: Address, id: U256) -> Result { let liquidity = self.pools.getter(pool).get_position_liquidity(id); Ok(liquidity.sys()) @@ -559,10 +568,10 @@ impl Pools { /// Requires the caller to be the position owner. Requires the pool to be enabled. /// Requires the length of the pools and ids to be equal. #[allow(non_snake_case)] - pub fn collect_7_F21947_C( + pub fn collect_7_F21947_C( &mut self, pools: Vec
, - ids: Vec + ids: Vec, ) -> Result, Revert> { assert_eq!(ids.len(), pools.len()); @@ -660,7 +669,7 @@ impl Pools { /// Refreshes and updates liquidity in a position, using approvals to transfer tokens. /// See [Self::update_position_internal]. #[allow(non_snake_case)] - pub fn update_position_622_A559_D( + pub fn update_position_622_A559_D( &mut self, pool: Address, id: U256, @@ -761,7 +770,7 @@ impl Pools { /// # Errors /// Requires the caller to be the seawater admin. Requires the pool to not exist. #[allow(non_snake_case)] - pub fn create_pool_D650_E2_D0( + pub fn create_pool_D650_E2_D0( &mut self, pool: Address, price: U256, @@ -795,25 +804,25 @@ impl Pools { /// Getter method for the sqrt price #[allow(non_snake_case)] - pub fn sqrt_price_x967_B8_F5_F_C5(&self, pool: Address) -> Result { + pub fn sqrt_price_x967_B8_F5_F_C5(&self, pool: Address) -> Result { Ok(self.pools.getter(pool).get_sqrt_price()) } /// Getter method for the current tick #[allow(non_snake_case)] - pub fn cur_tick181_C6_F_D9(&self, pool: Address) -> Result { + pub fn cur_tick181_C6_F_D9(&self, pool: Address) -> Result { // converted to i32 for automatic abi encoding Ok(self.pools.getter(pool).get_cur_tick().sys()) } #[allow(non_snake_case)] - pub fn fees_owed_22_F28_D_B_D(&self, pool: Address, id: U256) -> Result<(u128, u128), Revert> { + pub fn fees_owed_22_F28_D_B_D(&self, pool: Address, id: U256) -> Result<(u128, u128), Revert> { Ok(self.pools.getter(pool).get_fees_owed(id)) } /// Getter method for the tick spacing of the pool given. #[allow(non_snake_case)] - pub fn tick_spacing_653_F_E28_F(&self, pool: Address) -> Result { + pub fn tick_spacing_653_F_E28_F(&self, pool: Address) -> Result { // converted to i32 for automatic abi encoding Ok(self.pools.getter(pool).get_tick_spacing().sys()) } @@ -894,7 +903,7 @@ impl test_utils::StorageNew for Pools { #[cfg(test)] mod test { - use crate::{eth_serde, test_utils, types::I256Extension, types::*, Pools, tick_math}; + use crate::{eth_serde, test_utils, tick_math, types::I256Extension, types::*, Pools}; use maplit::hashmap; use ruint_macro::uint; use stylus_sdk::{ @@ -943,11 +952,6 @@ mod test { assert_eq!(data.len(), 0); } - #[test] - fn test_sqrt_price() { - dbg!(test_utils::encode_sqrt_price(100, 1)); - } - #[test] fn test_similar_to_ethers() -> Result<(), Vec> { test_utils::with_storage::<_, Pools, _>( @@ -1110,8 +1114,9 @@ mod test { let to = address!("de104342B32BCa03ec995f999181f7Cf1fFc04d7"); let amount = U256::from_str("10000000000").unwrap(); let min_out = U256::from(0); - let (_amount_in, _amount_out) = - contract.swap_2_exact_in_41203_F1_D(from, to, amount, min_out).unwrap(); + let (_amount_in, _amount_out) = contract + .swap_2_exact_in_41203_F1_D(from, to, amount, min_out) + .unwrap(); Ok(()) }, ) @@ -1182,7 +1187,19 @@ mod test { let sqrt_lower = tick_math::get_sqrt_ratio_at_tick(tick_lower)?; let sqrt_upper = tick_math::get_sqrt_ratio_at_tick(tick_upper)?; - dbg!(("update_position", liq, sqrt_price, tick_current, id, delta, tick_lower, tick_upper, sqrt_lower, sqrt_upper, sqrt_current)); + dbg!(( + "update_position", + liq, + sqrt_price, + tick_current, + id, + delta, + tick_lower, + tick_upper, + sqrt_lower, + sqrt_upper, + sqrt_current + )); // liquidity 0 // sqrt price 91912741289436239605563425905 diff --git a/pkg/seawater/src/pool.rs b/pkg/seawater/src/pool.rs index adf71ac2..d42d5ae3 100644 --- a/pkg/seawater/src/pool.rs +++ b/pkg/seawater/src/pool.rs @@ -192,7 +192,7 @@ impl StoragePool { amount_0_min: U256, amount_1_min: U256, amount_0_max: U256, - amount_1_max: U256 + amount_1_max: U256, ) -> Result<(I256, I256), Revert> { // calculate the delta using the amounts that we have here, guaranteeing // that we don't dip below the amount that's supplied as the minimum. @@ -211,7 +211,7 @@ impl StoragePool { sqrt_ratio_a_x_96, // lower_tick sqrt_ratio_b_x_96, // upper_tick amount_0_max, // amount_0 - amount_1_max // amount_1 + amount_1_max, // amount_1 )?; let (amount_0, amount_1) = self.update_position(id, delta)?; @@ -292,7 +292,6 @@ impl StoragePool { let mut iters = 0; while !state.amount_remaining.is_zero() && state.price != price_limit { iters += 1; - debug_assert!(iters != 100, "swapping didn't resolve after 100 iters!"); let step_initial_price = state.price; @@ -321,6 +320,7 @@ impl StoragePool { true => price_limit, false => step_next_price, }; + // step_fee_amount is reduced by protocol fee later let (next_sqrt_price, step_amount_in, step_amount_out, mut step_fee_amount) = swap_math::compute_swap_step( @@ -330,6 +330,7 @@ impl StoragePool { state.amount_remaining, fee, )?; + state.price = next_sqrt_price; // update state @@ -519,401 +520,391 @@ impl test_utils::StorageNew for StoragePool { #[cfg(test)] mod test { - use std::ops::{Mul, Neg}; + use std::ops::Neg; use super::*; use crate::test_utils; use ruint_macro::uint; - use stylus_sdk::alloy_primitives::I128; + use stylus_sdk::alloy_primitives::{Signed, I128}; #[test] fn test_update_position() { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |storage| { - storage - .init(test_utils::encode_sqrt_price(1, 10), 0, 1, u128::MAX) - .unwrap(); - - let id = uint!(2_U256); - - storage - .create_position(id, tick_math::get_min_tick(1), tick_math::get_max_tick(1)) - .unwrap(); - - assert_eq!( - storage.update_position(id, 3161), - Ok((I256::unchecked_from(9996), I256::unchecked_from(1000))), - ); - }, - ); + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |storage| { + storage + .init(test_utils::encode_sqrt_price(1, 10), 0, 1, u128::MAX) + .unwrap(); + + let id = uint!(2_U256); + + storage + .create_position(id, tick_math::get_min_tick(1), tick_math::get_max_tick(1)) + .unwrap(); + + assert_eq!( + storage.update_position(id, 3161), + Ok((I256::unchecked_from(9996), I256::unchecked_from(1000))), + ); + }); } #[test] fn test_update_position_2() { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |storage| { - storage - .init(test_utils::encode_sqrt_price(1, 10), 0, 1, u128::MAX) - .unwrap(); + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |storage| { + storage + .init(test_utils::encode_sqrt_price(1, 10), 0, 1, u128::MAX) + .unwrap(); - let id = uint!(2_U256); + let id = uint!(2_U256); - storage.create_position(id, -874753, -662914).unwrap(); + storage.create_position(id, -874753, -662914).unwrap(); - assert_eq!( - storage.update_position(id, 24703680000000000000000), - Ok((I256::unchecked_from(0), I256::unchecked_from(99649663))), - ); - }, - ); + assert_eq!( + storage.update_position(id, 24703680000000000000000), + Ok((I256::unchecked_from(0), I256::unchecked_from(99649663))), + ); + }); } #[test] fn test_swap() -> Result<(), Revert> { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |storage| { - storage.init( - test_utils::encode_sqrt_price(100, 1), // price - 0, - 1, - u128::MAX, - )?; + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |storage| { + storage.init( + test_utils::encode_sqrt_price(100, 1), // price + 0, + 1, + u128::MAX, + )?; + + let id = uint!(2_U256); + storage + .create_position( + id, + tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(50, 1))?, + tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(150, 1))?, + ) + .unwrap(); + storage.update_position(id, 100)?; + + let id = uint!(3_U256); + storage + .create_position( + id, + tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(80, 1))?, + tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(150, 1))?, + ) + .unwrap(); + storage.update_position(id, 100)?; + + storage.swap( + true, + I256::unchecked_from(-10), + test_utils::encode_sqrt_price(60, 1), + )?; + + storage.swap( + true, + I256::unchecked_from(10), + test_utils::encode_sqrt_price(50, 1), + )?; + + storage.swap( + false, + I256::unchecked_from(10), + test_utils::encode_sqrt_price(120, 1), + )?; + + storage.swap( + false, + I256::unchecked_from(-10000), + test_utils::encode_sqrt_price(120, 1), + )?; + + Ok(()) + }) + } - let id = uint!(2_U256); - storage - .create_position( - id, - tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(50, 1))?, - tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(150, 1))?, - ) - .unwrap(); - storage.update_position(id, 100)?; - - let id = uint!(3_U256); - storage - .create_position( - id, - tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(80, 1))?, - tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(150, 1))?, - ) - .unwrap(); - storage.update_position(id, 100)?; + #[test] + fn test_pool_init_state() -> Result<(), Revert> { + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |pool| { + let price = test_utils::encode_sqrt_price(100, 1); - storage.swap( - true, - I256::unchecked_from(-10), - test_utils::encode_sqrt_price(60, 1), - )?; + pool.init(price, 2, 1, u128::MAX)?; - storage.swap( - true, - I256::unchecked_from(10), - test_utils::encode_sqrt_price(50, 1), - )?; + assert_eq!(pool.enabled.get(), true); + assert_eq!(pool.sqrt_price.get(), price); - storage.swap( - false, - I256::unchecked_from(10), - test_utils::encode_sqrt_price(120, 1), - )?; + assert_eq!( + pool.cur_tick.get(), + I32::lib(&tick_math::get_tick_at_sqrt_ratio(price)?) + ); - storage.swap( - false, - I256::unchecked_from(-10000), - test_utils::encode_sqrt_price(120, 1), - )?; + assert_eq!(pool.fee.get(), U32::lib(&2)); - Ok(()) - }, - ) + assert_eq!(pool.tick_spacing.get(), U8::lib(&1)); + + assert_eq!(pool.max_liquidity_per_tick.get(), U128::lib(&u128::MAX)); + + Ok(()) + }) } #[test] - fn test_pool_init_state() -> Result<(), Revert> { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |pool| { - let price = test_utils::encode_sqrt_price(100, 1); - - pool.init( - price, // price - 2, - 1, - u128::MAX, - )?; - - assert_eq!(pool.enabled.get(), true); - assert_eq!(pool.sqrt_price.get(), price); + fn test_pool_init_reverts() -> Result<(), Revert> { + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |storage| { + match storage.init(uint!(1_U256), 0, 0, 0_u128) { + Err(r) => assert_eq!(Error::R.to_string(), String::from_utf8(r).unwrap()), + _ => panic!("expected R"), + } - assert_eq!( - pool.cur_tick.get(), - I32::lib(&tick_math::get_tick_at_sqrt_ratio(price)?) - ); + match storage.init(test_utils::encode_sqrt_price(100, 1), 0, 1, u128::MAX) { + Err(r) => assert_eq!( + Error::PoolAlreadyInitialised.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PoolAlreadyInitialised"), + } + Ok(()) + }) + } - assert_eq!(pool.fee.get(), U32::lib(&2)); + #[test] + fn test_pool_swaps_reverts() { + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |pool| { + let sqrt_price = test_utils::encode_sqrt_price(1, 1); + + match pool.swap(true, I256::unchecked_from(1), sqrt_price) { + Err(r) => assert_eq!( + Error::PoolDisabled.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PoolDisabled"), + } - assert_eq!(pool.tick_spacing.get(), U8::lib(&1)); + pool.init(sqrt_price, 1, 1, u128::MAX).unwrap(); - assert_eq!(pool.max_liquidity_per_tick.get(), U128::lib(&u128::MAX)); + match pool.swap(true, I256::unchecked_from(1), sqrt_price + U256::from(1)) { + Err(r) => assert_eq!( + Error::PriceLimitTooLow.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PriceLimitTooLow"), + } - Ok(()) - }, - ) - } + match pool.swap(true, I256::unchecked_from(1), tick_math::MIN_SQRT_RATIO) { + Err(r) => assert_eq!( + Error::PriceLimitTooLow.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PriceLimitTooLow"), + } - #[test] - fn test_pool_init_reverts() -> Result<(), Revert> { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |storage| { - match storage.init(uint!(1_U256), 0, 0, 0_u128) { - Err(r) => assert_eq!(Error::R.to_string(), String::from_utf8(r).unwrap()), - _ => panic!("expected R"), - } + match pool.swap(false, I256::unchecked_from(1), tick_math::MAX_SQRT_RATIO) { + Err(r) => assert_eq!( + Error::PriceLimitTooHigh.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PriceLimitTooHigh"), + } - match storage.init(test_utils::encode_sqrt_price(100, 1), 0, 1, u128::MAX) { - Err(r) => assert_eq!( - Error::PoolAlreadyInitialised.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PoolAlreadyInitialised"), - } - Ok(()) - }, - ) + match pool.swap(false, I256::unchecked_from(1), sqrt_price - U256::from(1)) { + Err(r) => assert_eq!( + Error::PriceLimitTooHigh.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PriceLimitTooHigh"), + } + }); } #[test] fn test_pool_position_create() -> Result<(), Revert> { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |pool| { - let id = uint!(2_U256); - let low = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(50, 1))?; - let up = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(150, 1))?; - - match pool.create_position(id, low, up) { - Err(r) => assert_eq!( - Error::PoolDisabled.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PoolDisabled"), - } + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |pool| { + let id = uint!(2_U256); + let low = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(50, 1))?; + let up = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price(150, 1))?; + + match pool.create_position(id, low, up) { + Err(r) => assert_eq!( + Error::PoolDisabled.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PoolDisabled"), + } - pool.init( - test_utils::encode_sqrt_price(100, 1), // price - 0, - 1, - u128::MAX, - )?; + pool.init( + test_utils::encode_sqrt_price(100, 1), // price + 0, + 10, + u128::MAX, + )?; + + match pool.create_position(id, 11, 17) { + Err(r) => assert_eq!( + Error::InvalidTickSpacing.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected InvalidTickSpacing"), + } - pool.create_position(id, low, up)?; + pool.create_position(id, low - low % 10, up - up % 10)?; - let position_saved = pool.positions.positions.get(id); + let position_saved = pool.positions.positions.get(id); - assert_eq!(position_saved.lower.get().as_i32(), low); - assert_eq!(position_saved.upper.get().as_i32(), up); + assert_eq!(position_saved.lower.get().as_i32(), low - low % 10); + assert_eq!(position_saved.upper.get().as_i32(), up - up % 10); - Ok(()) - }, - ) + Ok(()) + }) } #[test] fn test_pool_update_position_reverts() { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |pool| { - pool.init(test_utils::encode_sqrt_price(1, 10), 0, 1, u128::MAX) - .unwrap(); + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |pool| { + pool.init(test_utils::encode_sqrt_price(1, 10), 0, 1, u128::MAX) + .unwrap(); - let id = uint!(2_U256); + let id = uint!(2_U256); - pool.create_position(id, tick_math::get_min_tick(1), tick_math::get_max_tick(1)) - .unwrap(); + pool.create_position(id, tick_math::get_min_tick(1), tick_math::get_max_tick(1)) + .unwrap(); - pool.set_enabled(false); + pool.set_enabled(false); - match pool.update_position(id, 3161) { - Err(r) => assert_eq!( - Error::PoolDisabled.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PoolDisabled"), - } + match pool.update_position(id, 3161) { + Err(r) => assert_eq!( + Error::PoolDisabled.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PoolDisabled"), + } - match pool.update_position(id, 0) { - Err(r) => assert_eq!( - Error::PoolDisabled.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PoolDisabled"), - } - }, - ); + match pool.update_position(id, 0) { + Err(r) => assert_eq!( + Error::PoolDisabled.to_string(), + String::from_utf8(r).unwrap() + ), + _ => panic!("expected PoolDisabled"), + } + }); } #[test] fn test_pool_update_position_parametric() { - let prices = [ - [1, 1], - [1, 3], - [1, 1_000_000], - [3, 1], - [3, 5], - [1_000_000, 1], + let init_prices: [[i64; 2]; 9] = [ + [10, 10], + [10, 33], + [10, 171], + [10, 16_381], + [10, 1_048_572], + [33, 10], + [171, 10], + [16_381, 10], + [1_048_572, 10], ]; - let tick_spacing = [1, 100, 1000, 1_000_000]; + let position_ranges: [[i64; 2]; 3] = [[-20, -10], [-10, 10], [10, 20]]; - let fee = 1111; + let position_delta_template = [1000, 777, 252, 33, 5]; - let position_delta = - [1000, 777, 100, 33, 3].map(|n| I128::unchecked_from(n).mul(I128::exp10(17))); + let position_delta: Vec = (1..=20).map(|d| d * 10i128.pow(18)).collect(); - let swap_amount_denom = I256::unchecked_from(10); + for price in init_prices.iter() { + for delta in position_delta.iter() { + for position_range in position_ranges.iter() { + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |pool| { + let init_price = test_utils::encode_sqrt_price( + price[0].unsigned_abs(), + price[1].unsigned_abs(), + ); - for price in prices.iter() { - for tick in tick_spacing.iter() { - for delta in position_delta.iter() { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |pool| { - let sqrt_price = test_utils::encode_sqrt_price(price[0], price[1]); + let position_prices = [ + price[0] + price[0] * position_range[0] / 100, + price[0] + price[0] * position_range[1] / 100, + ]; - pool.init(sqrt_price, fee, *tick as u8, u128::MAX).unwrap(); + let low = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price( + position_prices[0].unsigned_abs(), + price[1].unsigned_abs(), + )) + .unwrap(); - let id = uint!(2_U256); + let up = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price( + position_prices[1].unsigned_abs(), + price[1].unsigned_abs(), + )) + .unwrap(); - pool.create_position( - id, - tick_math::get_min_tick(*tick as u8), - tick_math::get_max_tick(*tick as u8), - ) - .unwrap(); + pool.init(init_price, 3000, 60 as u8, u128::MAX).unwrap(); - pool.update_position(id, delta.unchecked_into()).unwrap(); + let id = uint!(2_U256); - pool.liquidity.set(delta.unchecked_into()); + let low_padded = low - low % 60; + let up_padded = up - up % 60; - let swap_amount = - I256::try_from(delta.to_string()).unwrap() / swap_amount_denom; + pool.create_position(id, low_padded, up_padded).unwrap(); - pool.swap(false, swap_amount, sqrt_price + U256::from(1)) - .unwrap(); + let liqudity = pool.liquidity.get().sys(); - pool.swap(true, swap_amount.neg(), sqrt_price).unwrap(); + let (u0, u1) = pool.update_position(id, *delta).unwrap(); - pool.swap(false, swap_amount, sqrt_price + U256::from(1)) - .unwrap(); + if position_prices[0] < price[0] && price[0] < position_prices[1] { + assert!(u0.gt(&I256::zero())); + assert!(u1.gt(&I256::zero())); + assert!(pool.liquidity.get().sys() - liqudity > 0); + pool.update_position(id, -delta).unwrap(); + assert!(pool.liquidity.get().sys() == 0); + } else { + assert!(pool.liquidity.get().sys() - liqudity == 0); + } + }); + } + } + } + } - pool.update_position(id, i128::from(0)).unwrap(); + #[test] + fn test_swap_inside_liq_range() -> Result<(), Revert> { + let pos_id = uint!(777_U256); - let position_after = pool.positions.positions.get(id); + let delta = 10i128.pow(18); - assert_eq!(position_after.token_owed_1.get(), U128::lib(&1)); - //TODO: check owed_fees calculations carfully + let init_price = test_utils::encode_sqrt_price(100_000, 1_000); - let delta_neg: i128 = delta.neg().unchecked_into(); + let liq_price_inside = [50_000, 150_000]; - pool.update_position(id, delta_neg).unwrap(); - }, - ); - } - } - } - } + //Swap up to 1% of the liquidity + let swap_amounts: Vec = (1..=10).map(|p| p * delta / 1_000).collect(); - fn test_pool_swaps_reverts() { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |pool| { - let sqrt_price = test_utils::encode_sqrt_price(1, 1); - - match pool.swap(true, I256::unchecked_from(1), sqrt_price) { - Err(r) => assert_eq!( - Error::PoolDisabled.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PoolDisabled"), - } + for swap_amount in &swap_amounts { + // Price inside liquidity range + test_utils::with_storage::<_, StoragePool, _>(None, None, None, None, |pool| { + pool.init(init_price, 3000, 60, u128::MAX).unwrap(); - pool.init(sqrt_price, 1, 1, u128::MAX).unwrap(); + let lower = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price( + liq_price_inside[0], + 1_000, + )) + .unwrap(); - match pool.swap(true, I256::unchecked_from(1), sqrt_price + U256::from(1)) { - Err(r) => assert_eq!( - Error::PriceLimitTooLow.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PriceLimitTooLow"), - } + let upper = tick_math::get_tick_at_sqrt_ratio(test_utils::encode_sqrt_price( + liq_price_inside[1], + 1_000, + )) + .unwrap(); - match pool.swap(true, I256::unchecked_from(1), tick_math::MIN_SQRT_RATIO) { - Err(r) => assert_eq!( - Error::PriceLimitTooLow.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PriceLimitTooLow"), - } + pool.create_position(pos_id, lower - lower % 60, upper - upper % 60) + .unwrap(); - match pool.swap(false, I256::unchecked_from(1), tick_math::MAX_SQRT_RATIO) { - Err(r) => assert_eq!( - Error::PriceLimitTooHigh.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PriceLimitTooHigh"), - } + pool.update_position(pos_id, delta).unwrap(); - match pool.swap(false, I256::unchecked_from(1), sqrt_price - U256::from(1)) { - Err(r) => assert_eq!( - Error::PriceLimitTooHigh.to_string(), - String::from_utf8(r).unwrap() - ), - _ => panic!("expected PriceLimitTooHigh"), - } - }, - ); - } + let (a0, a1, final_tick) = pool + .swap(true, I256::unchecked_from(*swap_amount), U256::MAX) + .unwrap(); - fn test_pool_swaps_parametric() { - test_utils::with_storage::<_, StoragePool, _>( - None, - None, // slots - None, // caller balances - None, // amm balances - |pool| { - //WIP - }, - ); + assert!(a0 == I256::unchecked_from(*swap_amount)); + }); + } + + Ok(()) } }