def mint_amounts_with_slippage(self, slippage_tolerance: Fraction) -> Tuple: """ Returns amount0; amount1 to mint after accounting for the given slippage_tolerance Virtual pools are created for instantiating Position entities that can be used to determine mint amounts """ assert isinstance(slippage_tolerance, Fraction) assert (1 > slippage_tolerance.float_quotient() > 0) sqrtRatioX96Lower, sqrtRatioX96Upper = self._ratios_after_slippage( slippage_tolerance) # create counterfactual pools with no liquidity pool_lower = Pool(self.pool.token_0, self.pool.token_1, self.pool.fee, sqrtRatioX96Lower, 0, get_tick_at_sqrt_ratio(sqrtRatioX96Lower), []) pool_upper = Pool(self.pool.token_0, self.pool.token_1, self.pool.fee, sqrtRatioX96Upper, 0, get_tick_at_sqrt_ratio(sqrtRatioX96Upper), []) position_to_create_amount_0, position_to_create_amount_1 = self.mint_amounts( ) position_to_create = Position.from_amounts( self.pool, self.tick_lower, self.tick_upper, position_to_create_amount_0, position_to_create_amount_1, False) # calculate mint amounts given the current tick and slippage adjusted liquidity amount_0 = Position(pool_upper, self.tick_lower, self.tick_upper, position_to_create.liquidity).mint_amounts()[0] amount_1 = Position(pool_lower, self.tick_lower, self.tick_upper, position_to_create.liquidity).mint_amounts()[1] return amount_0, amount_1
def test_get_tick_at_sqrt_ratio(): calculated_sqrt_price_ratio = encodeSqrtRatioX96(1, 1900) sqrt_price_ratio_expected = 1817618704642608503278368873 assert calculated_sqrt_price_ratio == sqrt_price_ratio_expected tick = get_tick_at_sqrt_ratio(calculated_sqrt_price_ratio) assert tick == -75500 assert get_tick_at_sqrt_ratio(MIN_SQRT_RATIO) == MIN_TICK assert get_tick_at_sqrt_ratio(MAX_SQRT_RATIO - 1) == MAX_TICK - 1
def get_tick_at_price(price) -> int: """ returns the first tick whose price is greater than or equal to the input tick price """ assert isinstance(price, PriceFraction) sorted = int(price.base_token.address.address, 16) < int( price.quote_token.address.address, 16) sqrt_ratio_x96 = encodeSqrtRatioX96( price.numerator, price.denominator) if sorted else encodeSqrtRatioX96( price.denominator, price.numerator) tick = get_tick_at_sqrt_ratio(sqrt_ratio_x96) next_tick_price = PriceFraction.get_price_at_tick( price.base_token, price.quote_token, tick + 1) if sorted: if not price.less_than(next_tick_price): tick += 1 else: if not price.greater_than(next_tick_price): tick += 1 return tick
def test_mint_token_pool_low_price_and_slippage(self): """ Test minting a position for a pool that is a small fraction """ test_token_1 = Token( "test_1", Address("0x0000000000000000000000000000000000000001"), 18) test_token_2 = Token( "test_2", Address("0x0000000000000000000000000000000000000002"), 18) token_1_balance = Wad.from_number(10) token_2_balance = Wad.from_number(100) # sqrt_price_ratio = self.get_starting_sqrt_ratio(3000, 1) sqrt_price_ratio = self.get_starting_sqrt_ratio( Wad.from_number(3000).value, Wad.from_number(1).value) current_tick = get_tick_at_sqrt_ratio(sqrt_price_ratio) ticks = [] test_pool = Pool(test_token_1, test_token_2, FEES.MEDIUM.value, sqrt_price_ratio, 0, current_tick, ticks) # set Position.from_amounts() params tick_lower = current_tick - TICK_SPACING.MEDIUM.value * 5 tick_upper = current_tick + TICK_SPACING.MEDIUM.value * 7 rounded_tick_lower = Tick.nearest_usable_tick( tick_lower, TICK_SPACING.MEDIUM.value) rounded_tick_upper = Tick.nearest_usable_tick( tick_upper, TICK_SPACING.MEDIUM.value) calculated_position = Position.from_amounts( test_pool, rounded_tick_lower, rounded_tick_upper, token_1_balance.value, token_2_balance.value, False) test_liquidity = calculated_position.liquidity test_position = Position(test_pool, rounded_tick_lower, rounded_tick_upper, test_liquidity) amount_0, amount_1 = test_position.mint_amounts() slippage_tolerance = Fraction(2, 100) amount_0_min, amount_1_min = test_position.mint_amounts_with_slippage( slippage_tolerance) # check that mint amounts will pass periphery contract assertions assert amount_0_min > 0 and amount_1_min > 0 assert amount_0_min < amount_0 and amount_1_min < amount_1
def test_liquidity_given_balance(self): """ Test liquidity and mint amount calculations """ test_token_1 = Token( "test_1", Address("0x0000000000000000000000000000000000000001"), 18) test_token_2 = Token( "test_2", Address("0x0000000000000000000000000000000000000002"), 6) token_1_balance = test_token_1.unnormalize_amount(Wad.from_number(10)) token_2_balance = test_token_2.unnormalize_amount(Wad.from_number(500)) sqrt_price_ratio = self.get_starting_sqrt_ratio( Wad.from_number(1).value, test_token_2.unnormalize_amount(Wad.from_number(3000)).value) current_tick = get_tick_at_sqrt_ratio(sqrt_price_ratio) ticks = [] test_pool = Pool(test_token_1, test_token_2, FEES.MEDIUM.value, sqrt_price_ratio, 0, current_tick, ticks) tick_lower = current_tick - TICK_SPACING.MEDIUM.value * 5 tick_upper = current_tick + TICK_SPACING.MEDIUM.value * 7 rounded_tick_lower = Tick.nearest_usable_tick( tick_lower, TICK_SPACING.MEDIUM.value) rounded_tick_upper = Tick.nearest_usable_tick( tick_upper, TICK_SPACING.MEDIUM.value) calculated_position = Position.from_amounts( test_pool, rounded_tick_lower, rounded_tick_upper, token_1_balance.value, token_2_balance.value, False) test_liquidity = calculated_position.liquidity assert test_liquidity == 252860870269028 test_position = Position(test_pool, rounded_tick_lower, rounded_tick_upper, test_liquidity) amount_0, amount_1 = test_position.mint_amounts() assert amount_0 == 95107120950731527 assert amount_1 == 208677042
def test_should_mint_with_nonstandard_decimals(self): """ mint a position with one of the tokens having nonstandard decimals. Verify that the positions price and minted amounts accounts for decimals. """ test_token_1 = Token( "test_1", Address("0x0000000000000000000000000000000000000001"), 18) test_token_2 = Token( "test_2", Address("0x0000000000000000000000000000000000000002"), 6) # instantiate test pool # sqrt_price_ratio = self.get_starting_sqrt_ratio(Wad.from_number(1).value, Wad.from_number(3500).value) sqrt_price_ratio = self.get_starting_sqrt_ratio(1, 3500) current_tick = get_tick_at_sqrt_ratio(sqrt_price_ratio) ticks = [] test_pool = Pool(test_token_1, test_token_2, FEES.MEDIUM.value, sqrt_price_ratio, 0, current_tick, ticks) # based upon current price (expressed in token1/token0), determine the tick to mint the position at tick_spacing = TICK_SPACING.MEDIUM.value desired_price = PriceFraction(test_token_1, test_token_2, 1, 3500) desired_tick = PriceFraction.get_tick_at_price(desired_price) # identify upper and lower tick bounds for the position desired_lower_tick = Tick.nearest_usable_tick( desired_tick - tick_spacing * 5, tick_spacing) desired_upper_tick = Tick.nearest_usable_tick( desired_tick + tick_spacing * 7, tick_spacing) # calculate amount to add for each position. ## since test_token_2 has 6 decimals, we must unnormalize the Wad amount from 18 -> 6 token_1_balance = Wad.from_number(10) token_2_balance = Wad.from_number(100) token_1_to_add = test_token_1.unnormalize_amount(token_1_balance).value token_2_to_add = test_token_2.unnormalize_amount(token_2_balance).value # token_1_to_add = token_1_balance.value # token_2_to_add = token_2_balance.value calculated_position = Position.from_amounts(test_pool, desired_lower_tick, desired_upper_tick, token_1_to_add, token_2_to_add, False) amount_0, amount_1 = calculated_position.mint_amounts() slippage_tolerance = Fraction(2, 100) amount_0_min, amount_1_min = calculated_position.mint_amounts_with_slippage( slippage_tolerance) # check that mint amounts will pass periphery contract assertions assert amount_0 > 0 and amount_1 > 0 assert amount_0_min > 0 and amount_1_min > 0 assert amount_0_min < amount_0 and amount_1_min < amount_1 # assume pool.tick_current < desired_upper_tick expected_amount_0 = SqrtPriceMath.get_amount_0_delta( test_pool.square_root_ratio_x96, get_sqrt_ratio_at_tick(desired_upper_tick), calculated_position.liquidity, True) expected_amount_1 = SqrtPriceMath.get_amount_1_delta( get_sqrt_ratio_at_tick(desired_lower_tick), test_pool.square_root_ratio_x96, calculated_position.liquidity, True) assert amount_0 == expected_amount_0 assert amount_1 == expected_amount_1 # get amounts from liquidity price_lower_tick = pow(1.0001, calculated_position.tick_lower) price_upper_tick = pow(1.0001, calculated_position.tick_upper) assert price_lower_tick < 3500 < price_upper_tick position_token_0 = calculated_position.liquidity / math.sqrt( price_upper_tick) position_token_1 = calculated_position.liquidity * math.sqrt( price_lower_tick) # compare original sqrt_price_ratio_x96 to the ratio determined by liquidity to mint assert str(sqrt_price_ratio)[:2] == str( encodeSqrtRatioX96(int(position_token_1), int(position_token_0)))[:2] assert sqrt_price_ratio // Q96 == encodeSqrtRatioX96( int(position_token_1), int(position_token_0)) // (2**96)
def swap(self, zero_or_one: bool, swap_amount: int, sqrt_price_limit_x96: int) -> dict: """ Calculate a swap and output pool state @param zero_or_one boolean indicating swapping in token_0 or token_1 @param swap_amount integer amount of the given token to be swapped @param sqrt_price_limit_x96 price limit that can't be breached following swap reserve changes @returns dictionary of resulting pool state {amount_calculated, sqrt_price_x96, liquidity, tick_current} """ assert isinstance(zero_or_one, bool) assert isinstance(swap_amount, int) assert (isinstance(sqrt_price_limit_x96, int) or sqrt_price_limit_x96 is None) if sqrt_price_limit_x96 is None: sqrt_price_limit_x96 = MIN_SQRT_RATIO + 1 if zero_or_one else MAX_SQRT_RATIO - 1 if zero_or_one: assert sqrt_price_limit_x96 > MIN_SQRT_RATIO assert sqrt_price_limit_x96 < self.square_root_ratio_x96 else: assert sqrt_price_limit_x96 < MAX_SQRT_RATIO assert sqrt_price_limit_x96 > self.square_root_ratio_x96 exact_input = swap_amount >= 0 pool_swap_state = { "swap_amount_remaining": swap_amount, "amount_calculated": 0, "sqrt_price_x96": self.square_root_ratio_x96, "tick": self.tick_current, "liquidity": self.liquidity } # loop through available ticks until the desired swap amount has been met, or available liquidity has been exhausted while pool_swap_state["swap_amount_remaining"] != 0 and pool_swap_state[ "sqrt_price_x96"] != sqrt_price_limit_x96: tick_next, tick_initalized = Tick.next_initialized_tick_within_word( self.ticks, pool_swap_state["tick"], zero_or_one, self.tick_spacing) step_state = { "sqrt_price_start_x96": pool_swap_state["sqrt_price_x96"], "tick_next": tick_next, "tick_initalized": tick_initalized } # check to see if swap would reach the end of the space if step_state["tick_next"] < MIN_TICK: step_state["tick_next"] = MIN_TICK elif step_state["tick_next"] > MAX_TICK: step_state["tick_next"] = MAX_TICK # identify price at the next tick with available liquidity step_state["sqrt_price_next_x96"] = get_sqrt_ratio_at_tick( step_state["tick_next"]) # calculate which target price to use when computing where the next swap will lead the pool state if zero_or_one: use_price_limit = step_state[ "sqrt_price_next_x96"] < sqrt_price_limit_x96 else: use_price_limit = step_state[ "sqrt_price_next_x96"] > sqrt_price_limit_x96 target_price = sqrt_price_limit_x96 if use_price_limit else step_state[ "sqrt_price_next_x96"] pool_swap_state["sqrt_price_x96"], step_state[ "amount_in"], step_state["amount_out"], step_state[ "fee_amount"] = compute_swap_step( pool_swap_state["sqrt_price_x96"], target_price, pool_swap_state["liquidity"], pool_swap_state["swap_amount_remaining"], self.fee) if exact_input: pool_swap_state["swap_amount_remaining"] = pool_swap_state[ "swap_amount_remaining"] - (step_state["amount_in"] + step_state["fee_amount"]) pool_swap_state["amount_calculated"] = pool_swap_state[ "amount_calculated"] - step_state["amount_out"] else: pool_swap_state["swap_amount_remaining"] = pool_swap_state[ "swap_amount_remaining"] + step_state["amount_out"] pool_swap_state["amount_calculated"] = pool_swap_state[ "amount_calculated"] + step_state[ "amount_in"] + step_state["fee_amount"] if pool_swap_state["sqrt_price_x96"] == step_state[ "sqrt_price_next_x96"]: if step_state["tick_initalized"]: net_liquidity = Tick.get_tick( self.ticks, step_state["tick_next"]).liquidity_net # when moving left on the tick map, liquidity_net becomes negative if zero_or_one: net_liquidity = net_liquidity * -1 pool_swap_state["liquidity"] = add_liquidity_delta( pool_swap_state["liquidity"], net_liquidity) pool_swap_state["tick"] = step_state[ "tick_next"] - 1 if zero_or_one else step_state["tick_next"] elif pool_swap_state["sqrt_price_x96"] != step_state[ "sqrt_price_start_x96"]: pool_swap_state["tick"] = get_tick_at_sqrt_ratio( pool_swap_state["sqrt_price_x96"]) return { "amount_calculated": pool_swap_state["amount_calculated"], "sqrt_price_x96": pool_swap_state["sqrt_price_x96"], "liquidity": pool_swap_state["liquidity"], "tick_current": pool_swap_state["tick"] }