diff --git a/tests/test_uniswap.py b/tests/test_uniswap.py index f969375..01a7a84 100644 --- a/tests/test_uniswap.py +++ b/tests/test_uniswap.py @@ -377,6 +377,32 @@ def test_get_tvl_in_pool_on_chain(self, client: Uniswap, tokens, token0, token1) assert tvl_0 > 0 assert tvl_1 > 0 + @pytest.mark.parametrize("token0, token1", [("DAI", "USDC")]) + def test_asset_locked_per_tick_sums_to_tvl(self, client: Uniswap, tokens, token0, token1): + if client.version != 3: + pytest.skip("Not supported in this version of Uniswap") + + pool = client.get_pool_instance(tokens[token0], tokens[token1]) + asset_locked_per_tick_dict = client.get_asset_locked_per_tick_in_pool(pool) + + # check TVL adds up correctly + token0_total = 0 + token1_total = 0 + ticks = asset_locked_per_tick_dict['ticks'] + token0_arr = asset_locked_per_tick_dict['token0'] + token1_arr = asset_locked_per_tick_dict['token1'] + + for i, tick in enumerate(ticks): + token0_total += token0_arr[i] + token1_total += token1_arr[i] + + tvl_0, tvl_1 = client.get_tvl_in_pool(pool) + + # assert on values rounded to nearest million for now TODO: fix + assert round(tvl_0 / 1e6) == round(token0_total / 1e6) + assert round(tvl_1 / 1e6) == round(token1_total / 1e6) + assert round((tvl_0 + tvl_1) / 1e6) == round((token0_total + token1_total) / 1e6) + @pytest.mark.skip @pytest.mark.parametrize( "token, max_eth", diff --git a/uniswap/constants.py b/uniswap/constants.py index e6d3f89..3eac4d2 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -64,7 +64,7 @@ MAX_TICK = -MIN_TICK # Source: https://github.com/Uniswap/v3-core/blob/v1.0.0/contracts/UniswapV3Factory.sol#L26-L31 -_tick_spacing = {100:1, 500: 10, 3_000: 60, 10_000: 200} +_tick_spacing = {100: 1, 500: 10, 3_000: 60, 10_000: 200} # Derived from (MIN_TICK//tick_spacing) >> 8 and (MAX_TICK//tick_spacing) >> 8 -_tick_bitmap_range = {100:(-3466, 3465), 500: (-347, 346), 3_000: (-58, 57), 10_000: (-18, 17)} +_tick_bitmap_range = {100: (-3466, 3465), 500: (-347, 346), 3_000: (-58, 57), 10_000: (-18, 17)} diff --git a/uniswap/uniswap.py b/uniswap/uniswap.py index ae5ad16..deda008 100644 --- a/uniswap/uniswap.py +++ b/uniswap/uniswap.py @@ -1385,6 +1385,101 @@ def get_tvl_in_pool(self, pool: Contract) -> Tuple[float, float]: token1_liquidity = token1_liquidity // (10**token1_decimals) return (token0_liquidity, token1_liquidity) + def get_asset_locked_per_tick_in_pool(self, pool: Contract) -> Dict: + """ + Iterate through each tick in a pool and calculate the amount of asset + locked on-chain per tick + + Note: the output of this function may differ from what is returned by the + UniswapV3 subgraph api (https://github.com/Uniswap/v3-subgraph/issues/74) + + Params + ------ + pool: Contract + pool contract instance to find TVL + """ + pool_tick_output_types = ( + "uint128", + "int128", + "uint256", + "uint256", + "int56", + "uint160", + "uint32", + "bool", + ) + + pool_immutables = self.get_pool_immutables(pool) + pool_state = self.get_pool_state(pool) + fee = pool_immutables["fee"] + sqrtPrice = pool_state["sqrtPriceX96"] / (1 << 96) + + TICK_SPACING = _tick_spacing[fee] + BITMAP_SPACING = _tick_bitmap_range[fee] + + _max_tick = self.find_tick_from_bitmap(BITMAP_SPACING, pool, TICK_SPACING, fee, True) + _min_tick = self.find_tick_from_bitmap(BITMAP_SPACING, pool, TICK_SPACING, fee, False) + + assert _max_tick != False, "Error finding max tick" + assert _min_tick != False, "Error finding min tick" + + # # Correcting for each token's respective decimals + token0_decimals = ( + _load_contract_erc20(self.w3, pool_immutables["token0"]) + .functions.decimals() + .call() + ) + token1_decimals = ( + _load_contract_erc20(self.w3, pool_immutables["token1"]) + .functions.decimals() + .call() + ) + Batch = namedtuple("Batch", "ticks batchResults") + ticks = [] + # Batching pool.functions.tick() calls as these are the major bottleneck to performance + for batch in list(chunks(range(_min_tick, _max_tick, TICK_SPACING), 1000)): + _batch = [] + _ticks = [] + for tick in batch: + _batch.append( + ( + pool.address, + HexBytes(pool.functions.ticks(tick)._encode_transaction_data()), + ) + ) + _ticks.append(tick) + ticks.append(Batch(_ticks, self.multicall(_batch, pool_tick_output_types))) + + liquidity_total = 0 + liquidity_per_tick_dict: Dict = { + 'ticks': [], + 'token0': [], + 'token1': [] + } + for tickBatch in ticks: + tick_arr = tickBatch.ticks + for i in range(len(tick_arr)): + tick = tick_arr[i] + tickData = tickBatch.batchResults[i] + # source: https://stackoverflow.com/questions/71814845/how-to-calculate-uniswap-v3-pools-total-value-locked-tvl-on-chain + liquidityNet = tickData[1] + liquidity_total += liquidityNet + sqrtPriceLow = 1.0001 ** (tick // 2) + sqrtPriceHigh = 1.0001 ** ((tick + TICK_SPACING) // 2) + token0_liquidity = self.get_token0_in_pool( + liquidity_total, sqrtPrice, sqrtPriceLow, sqrtPriceHigh + ) + token1_liquidity = self.get_token1_in_pool( + liquidity_total, sqrtPrice, sqrtPriceLow, sqrtPriceHigh + ) + token0_liquidity = token0_liquidity // (10**token0_decimals) + token1_liquidity = token1_liquidity // (10**token1_decimals) + liquidity_per_tick_dict['ticks'].append(tick) + liquidity_per_tick_dict['token0'].append(token0_liquidity) + liquidity_per_tick_dict['token1'].append(token1_liquidity) + + return liquidity_per_tick_dict + # ------ Approval Utils ------------------------------------------------------------ def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> None: """Give an exchange/router max approval of a token.""" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy