예제 #1
0
def mint_weth_dai_kovan():
    kovan_weth_balance = ERC20Token(web3=web3, address=weth_token_kovan.address).balance_of(account_address)
    kovan_dai_balance = ERC20Token(web3=web3, address=dai_token_kovan.address).balance_of(account_address)

    desired_lower_tick = Tick.nearest_usable_tick(
        weth_dai_kovan_pool.tick_current - (weth_dai_kovan_pool.tick_spacing * 5), weth_dai_kovan_pool.tick_spacing)
    desired_upper_tick = Tick.nearest_usable_tick(
        weth_dai_kovan_pool.tick_current + (weth_dai_kovan_pool.tick_spacing * 3), weth_dai_kovan_pool.tick_spacing)
    slippage_tolerance = Fraction(2, 100)
    deadline = int(time.time() + 1000)

    weth_to_add = kovan_weth_balance / Wad.from_number(2)
    dai_to_add = kovan_dai_balance / Wad.from_number(2)
    kovan_position_to_mint = Position.from_amounts(weth_dai_kovan_pool, desired_lower_tick, desired_upper_tick,
                                                   weth_to_add.value, dai_to_add.value, False)

    print("position_to_mint liquidity", kovan_position_to_mint.liquidity)

    # approve kovan tokens for usage by PositionManager
    position_manager_kovan.approve(weth_token_kovan)
    position_manager_kovan.approve(dai_token_kovan)

    kovan_mint_params = MintParams(web3, NonfungiblePositionManager_abi, kovan_position_to_mint, account_address,
                                   slippage_tolerance, deadline)
    kovan_mint_receipt = position_manager_kovan.mint(kovan_mint_params).transact()

    assert kovan_mint_receipt is not None and kovan_mint_receipt.successful
    print("tx receipt", kovan_mint_receipt.transaction_hash.hex())
예제 #2
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
예제 #3
0
    def generate_mint_usdcdai_params(self, pool: Pool) -> MintParams:

        deadline = int(time.time()) + 1000
        position = Position(pool, -10, 10, 1)
        slippage_tolerance = Fraction(20, 100)
        recipient = self.our_address

        mint_params = MintParams(self.web3,
                                 self.NonfungiblePositionManager_abi, position,
                                 recipient, slippage_tolerance, deadline)
        return mint_params
예제 #4
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
예제 #5
0
def mint_weth_usdc_kovan():

    # returned balance from balance_of shows amounts normalized to 18 decimals, so unnormalization to USDC 6 decimals is needed
     kovan_weth_balance = ERC20Token(web3=web3, address=weth_token_kovan.address).balance_of(account_address)
     kovan_usdc_balance = ERC20Token(web3=web3, address=usdc_token_kovan.address).balance_of(account_address)

     print("balance from balance_of, WETH: ", kovan_weth_balance, "USDC: ", kovan_usdc_balance)
     current_tick = weth_usdc_kovan_pool.tick_current
     current_pool_price = PriceFraction.get_price_at_tick(weth_token_kovan, usdc_token_kovan, weth_usdc_kovan_pool.tick_current)

     print("current pool tick", weth_usdc_kovan_pool.tick_current)
     print("current pool price", current_pool_price.float_quotient())

     desired_price = PriceFraction(weth_token_kovan, usdc_token_kovan, Wad.from_number(1).value, usdc_token_kovan.unnormalize_amount(Wad.from_number(2800)).value)
     desired_tick = PriceFraction.get_tick_at_price(desired_price)
     print("desired tick and price", desired_tick, desired_price.float_quotient())

     desired_lower_tick = Tick.nearest_usable_tick(
          desired_tick - (weth_usdc_kovan_pool.tick_spacing * 5), weth_usdc_kovan_pool.tick_spacing)
     desired_upper_tick = Tick.nearest_usable_tick(
          desired_tick + (weth_usdc_kovan_pool.tick_spacing * 3), weth_usdc_kovan_pool.tick_spacing)
     slippage_tolerance = Fraction(2, 100)
     deadline = int(time.time() + 1000)

     # weth_to_add = kovan_weth_balance / Wad.from_number(2)
     # usdc_to_add = usdc_token_kovan.normalize_amount(kovan_usdc_balance) / Wad.from_number(2)
     weth_to_add = kovan_weth_balance / Wad.from_number(2)
     usdc_to_add = kovan_usdc_balance / Wad.from_number(2)

     print("pool token 0: ", weth_usdc_kovan_pool.token_0.name)

     print("weth to add: ", weth_to_add, "usdc to add: ", usdc_to_add)
     kovan_position_to_mint = Position.from_amounts(weth_usdc_kovan_pool, desired_lower_tick, desired_upper_tick,
                                                    usdc_to_add.value, weth_to_add.value, False)

     print("position_to_mint liquidity", kovan_position_to_mint.liquidity)

     # approve kovan tokens for usage by PositionManager
     position_manager_kovan.approve(weth_token_kovan)
     position_manager_kovan.approve(usdc_token_kovan)

     kovan_mint_params = MintParams(web3, NonfungiblePositionManager_abi, kovan_position_to_mint, account_address,
                                    slippage_tolerance, deadline)
     kovan_mint_receipt = position_manager_kovan.mint(kovan_mint_params).transact()

     assert kovan_mint_receipt is not None and kovan_mint_receipt.successful
     print("tx receipt", kovan_mint_receipt.transaction_hash.hex())
예제 #6
0
    def test_mint_eth_token_pool(self, position_manager_helpers):
        """ Integration test to mint a pool where one side is WETH """
        position_manager_helper = position_manager_helpers(
            self.web3, self.position_manager,
            self.NonfungiblePositionManager_abi, self.token_weth,
            self.token_dai)

        # starting pool price for weth-dai 1900
        pool = position_manager_helper.create_and_initialize_pool(
            self.get_starting_sqrt_ratio(1, 1900), FEES.MEDIUM.value)

        # wrap ETH into WETH as UniV3 only works with ERC20 tokens
        position_manager_helper.wrap_eth(Wad.from_number(1), self.our_address)

        mint_params = position_manager_helper.generate_mint_params(
            pool, Position(pool, -60, 60, 10), self.our_address,
            Fraction(20, 100))

        mint_receipt = self.position_manager.mint(mint_params).transact()
        assert mint_receipt is not None and mint_receipt.successful
예제 #7
0
    def test_mint_token_pool(self, position_manager_helpers):
        """ Integration test to mint a pool with two ERC20 tokens """
        # create pool
        position_manager_helper = position_manager_helpers(
            self.web3, self.position_manager,
            self.NonfungiblePositionManager_abi, self.token_dai,
            self.token_usdc)
        pool = position_manager_helper.create_and_initialize_pool(
            self.get_starting_sqrt_ratio(1, 1), FEES.LOW.value)

        # generate MintParam
        mint_params = position_manager_helper.generate_mint_params(
            pool, Position(pool, -10, 10, 10), self.our_address,
            Fraction(1, 100))

        # mint new position
        gas_price = FixedGasPrice(gas_price=20000000000000000)
        mint_receipt = self.position_manager.mint(mint_params).transact(
            gas_price=gas_price)
        assert mint_receipt is not None and mint_receipt.successful
예제 #8
0
    def deploy_and_mint_weth_dai(self, position_manager_helpers) -> Pool:
        # deploy weth_dai pool and mint initial liquidity to swap against
        position_manager_helper_wethdai = position_manager_helpers(self.web3, self.position_manager,
                                                                   self.NonfungiblePositionManager_abi, self.token_weth,
                                                                   self.token_dai)

        starting_price = self.get_starting_sqrt_ratio(1, 1900)
        weth_dai_pool = position_manager_helper_wethdai.create_and_initialize_pool(
            starting_price, FEES.MEDIUM.value)

        # wrap weth for testing (univ3 only uses weth)
        position_manager_helper_wethdai.wrap_eth(Wad.from_number(25), self.our_address)



        tick_lower = weth_dai_pool.tick_current - TICK_SPACING.MEDIUM.value * 3
        tick_upper = weth_dai_pool.tick_current + TICK_SPACING.MEDIUM.value * 5
        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)

        # TODO: calculate liquidity at a given price, with available balances
        # current liquidity levels result in too small of a balance being minted - need to determine why higher liquidity fails price slippage check
        liquidity_to_mint = 900000000000000

        weth_dai_mint_params = position_manager_helper_wethdai.generate_mint_params(weth_dai_pool,
                                                                                    Position(weth_dai_pool, rounded_tick_lower, rounded_tick_upper,
                                                                                             9000000000000000000), self.our_address,
                                                                                    Fraction(20, 100))
        weth_dai_mint_receipt = self.position_manager.mint(weth_dai_mint_params).transact()
        assert weth_dai_mint_receipt is not None and weth_dai_mint_receipt.successful

        # mint_result = weth_dai_mint_receipt.result[0]
        # print("mint result", mint_result, mint_result.liquidity, mint_result.tick_lower, mint_result.tick_upper)
        token_id = weth_dai_mint_receipt.result[0].token_id
        print("minted_pool token_id", token_id)
        minted_position = self.position_manager.positions(token_id, weth_dai_pool.token_0, weth_dai_pool.token_1)
        print("minted weth_dai value", self.position_manager.price_position(token_id, 1900))
        return minted_position.pool
예제 #9
0
    def deploy_and_mint_dai_usdc(self, position_manager_helpers) -> Pool:
        # deploy dai_usdc pool and mint initial liquidity to swap against
        position_manager_helper_daiusdc = position_manager_helpers(self.web3, self.position_manager,
                                                                   self.NonfungiblePositionManager_abi, self.token_dai,
                                                                   self.token_usdc)

        dai_usdc_pool = position_manager_helper_daiusdc.create_and_initialize_pool(self.get_starting_sqrt_ratio(1, 1),
                                                                                        FEES.LOW.value)

        liquidity_to_mint = 1000000000
        dai_usdc_mint_params = position_manager_helper_daiusdc.generate_mint_params(dai_usdc_pool,
                                                                                    Position(dai_usdc_pool, -10, 10,
                                                                                             900000000000000000), self.our_address,
                                                                                    Fraction(10, 100))
        dai_usdc_mint_receipt = self.position_manager.mint(dai_usdc_mint_params).transact()
        assert dai_usdc_mint_receipt is not None and dai_usdc_mint_receipt.successful


        token_id = dai_usdc_mint_receipt.result[0].token_id
        print("minted_pool token_id", token_id)
        minted_position = self.position_manager.positions(token_id, dai_usdc_pool.token_0, dai_usdc_pool.token_1)
        print("minted dai_usdc value", self.position_manager.price_position(token_id, 1))
        return minted_position.pool
예제 #10
0
    def positions(self, token_id: int, token_0: Token,
                  token_1: Token) -> Position:
        """ Return an instantiated Position entity for a given positions token_id, and token pair

            pool information is retrieved with getters defined: https://github.com/Uniswap/uniswap-v3-core/blob/main/contracts/interfaces/pool/IUniswapV3PoolState.sol
        """
        assert (isinstance(token_id, int))
        assert isinstance(token_0, Token)
        assert isinstance(token_1, Token)

        position = self.get_position_info(token_id)
        assert token_0.address == Address(
            position[2]) and token_1.address == Address(position[3])

        fee = position[4]
        pool_address = self.get_pool_address(token_0, token_1, fee)
        pool_contract = self.get_pool_contract(pool_address)
        pool_state = self.get_pool_state(pool_contract)

        price_sqrt_ratio_x96 = pool_state[0]
        tick_current = pool_state[1]

        # get current initialized tick list
        tick_lower = position[5]
        tick_upper = position[6]
        tick_spacing = TICK_SPACING[FEES(fee).name].value
        ticks = self.get_initialized_ticklist(tick_lower, tick_upper,
                                              tick_spacing, pool_address)

        # get current in range liquidity
        pool_liquidity = pool_contract.functions.liquidity().call()
        pool = Pool(token_0, token_1, fee, price_sqrt_ratio_x96,
                    pool_liquidity, tick_current, ticks)

        position_liquidity = position[7]

        return Position(pool, tick_lower, tick_upper, position_liquidity)
예제 #11
0
    def test_collect_exact_output_swap(self, position_manager_helpers):
        """ Integration test of minting a new position, executing an ExactOutput swap against the positions liquidity
            to ensure fees are available, and then collecting those fees.
        """
        # create and intialize pool
        position_manager_helper = position_manager_helpers(
            self.web3, self.position_manager,
            self.NonfungiblePositionManager_abi, self.token_dai,
            self.token_usdc)
        pool = position_manager_helper.create_and_initialize_pool(
            self.get_starting_sqrt_ratio(1, 1), FEES.LOW.value)

        # mint initial liquidity
        mint_params = position_manager_helper.generate_mint_params(
            pool, Position(pool, -10, 10, 100000000000000), self.our_address,
            Fraction(20, 100))
        mint_receipt = self.position_manager.mint(mint_params).transact()

        # get the token_id out of the mint transaction receipt
        token_id = mint_receipt.result[0].token_id
        minted_position = self.position_manager.positions(
            token_id, pool.token_0, pool.token_1)

        # execute swaps against the pool to generate fees
        amount_out = Wad.from_number(10)
        slippage_tolerance = Fraction(20, 100)  # equivalent to 0.2
        recipient = self.our_address
        deadline = int(time.time() + 10000)

        # Build Route and Trade entities that can be used to determine amount_in
        route = Route([minted_position.pool], minted_position.pool.token_0,
                      minted_position.pool.token_1)
        trade = Trade.from_route(
            route,
            CurrencyAmount.from_raw_amount(minted_position.pool.token_1,
                                           amount_out.value),
            TRADE_TYPE.EXACT_OUTPUT_SINGLE.value)

        max_amount_in = trade.maximum_amount_in(slippage_tolerance).quotient()
        trade_amount_in = trade.input_amount.quotient()
        print("alts", max_amount_in, trade_amount_in)
        sqrt_price_limit = 100000000000000000000000
        amount_in = self.swap_router.quote_exact_output_single(
            pool.token_0, pool.token_1, pool.fee, amount_out.value,
            sqrt_price_limit)

        # amount_in = trade.input_amount.quotient()

        # Instantiate ExactOutputSingleParams that will be used to generate fees
        exact_output_single_params = ExactOutputSingleParams(
            self.web3, self.SwapRouter_abi, trade.route.token_path[0],
            trade.route.token_path[1], trade.route.pools[0].fee, recipient,
            deadline, amount_out.value, amount_in, sqrt_price_limit)
        swap = self.swap_router.swap_exact_output_single(
            exact_output_single_params).transact()
        assert swap is not None and swap.successful

        position_amount_0, position_amount_1 = self.position_manager.get_position_reserves(
            token_id)

        # collect fees from position
        collect_params = CollectParams(self.web3,
                                       self.NonfungiblePositionManager_abi,
                                       token_id, self.our_address,
                                       int(position_amount_0),
                                       int(position_amount_1))
        collect_receipt = self.position_manager.collect(
            collect_params).transact()

        assert collect_receipt is not None and collect_receipt.successful
예제 #12
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)