Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions tests/test_uniswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions uniswap/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)}
95 changes: 95 additions & 0 deletions uniswap/uniswap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down