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 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 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
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 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_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
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
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 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
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)
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
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)