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())
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())
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_nearest_usable_tick(): # given current_tick = 74999 tick_spacing = TICK_SPACING.MEDIUM.value # when rounded_tick = Tick.nearest_usable_tick(current_tick, tick_spacing) # then assert rounded_tick % tick_spacing == 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
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"] }
def _tick_mapper(self, tick) -> Tick: if isinstance(tick, Tick): return tick else: return Tick(tick[0], tick[1], tick[2])