Beispiel #1
0
    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
Beispiel #2
0
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
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
    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
Beispiel #6
0
    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)
Beispiel #7
0
    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"]
        }