From a34dcb93758ab408d4d40275bd3019c698c257f1 Mon Sep 17 00:00:00 2001 From: Hieu Nguyen Date: Sat, 22 Jul 2023 04:12:59 +0300 Subject: [PATCH] Get onchain price of Uniswap v3 pool (#138) * Add util func to get onchain price of uni v3 pool * Add test and changelog --- CHANGELOG.md | 5 +++++ eth_defi/uniswap_v3/pool.py | 7 ++++++- eth_defi/uniswap_v3/price.py | 23 +++++++++++++++++++++++ tests/uniswap_v3/test_uniswap_v3_price.py | 19 +++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0185e91..016c2286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.21.9 + +- Add utility function `get_onchain_price()` to ask on-chain price of a + Uniswap v3 pool at any given block number + # 0.21.8 - Add test coverage for `extract_timestamps_json_rpc_lazy` diff --git a/eth_defi/uniswap_v3/pool.py b/eth_defi/uniswap_v3/pool.py index 3991e049..38eaed55 100644 --- a/eth_defi/uniswap_v3/pool.py +++ b/eth_defi/uniswap_v3/pool.py @@ -1,10 +1,11 @@ """Uniswap v3 pool data.""" -from decimal import Decimal from dataclasses import dataclass +from decimal import Decimal from typing import Union from eth_typing import HexAddress from web3 import Web3 +from web3.contract import Contract from eth_defi.abi import get_deployed_contract from eth_defi.token import TokenDetails, fetch_erc20_details @@ -33,6 +34,9 @@ class PoolDetails: #: Pool fee as % multiplier, 1 = 100% fee: float + #: Pool contract proxy + pool: Contract + def __repr__(self): return f"Pool {self.address} is {self.token0.symbol}-{self.token1.symbol}, with the fee {self.fee * 100:.04f}%" @@ -72,6 +76,7 @@ def fetch_pool_details(web3, pool_contact_address: Union[str, HexAddress]) -> Po token1, raw_fee, raw_fee / 1_000_000, + pool, ) diff --git a/eth_defi/uniswap_v3/price.py b/eth_defi/uniswap_v3/price.py index 07a8d132..ffb4c26d 100644 --- a/eth_defi/uniswap_v3/price.py +++ b/eth_defi/uniswap_v3/price.py @@ -3,8 +3,10 @@ from decimal import Decimal from eth_typing import HexAddress +from web3 import Web3 from eth_defi.uniswap_v3.deployment import UniswapV3Deployment +from eth_defi.uniswap_v3.pool import fetch_pool_details from eth_defi.uniswap_v3.utils import encode_path @@ -216,3 +218,24 @@ def estimate_sell_received_amount( return amount, current_block return amount + + +def get_onchain_price( + web3: Web3, + pool_contract_address: str, + *, + block_identifier: int | None = None, + reverse_token_order: bool = False, +): + """Get the current price of a Uniswap pool. + + :param web3: Web3 instance + :param pool_contract_address: Contract address of the pool + :param block_identifier: A specific block to query price + :param reverse_token_order: If set, assume quote token is token0 + :return: Current price + """ + pool_details = fetch_pool_details(web3, pool_contract_address) + _, tick, *_ = pool_details.pool.functions.slot0().call(block_identifier=block_identifier) + + return pool_details.convert_price_to_human(tick, reverse_token_order) diff --git a/tests/uniswap_v3/test_uniswap_v3_price.py b/tests/uniswap_v3/test_uniswap_v3_price.py index 04933acc..65372923 100644 --- a/tests/uniswap_v3/test_uniswap_v3_price.py +++ b/tests/uniswap_v3/test_uniswap_v3_price.py @@ -20,6 +20,7 @@ UniswapV3PriceHelper, estimate_buy_received_amount, estimate_sell_received_amount, + get_onchain_price, ) from eth_defi.uniswap_v3.utils import get_default_tick_range @@ -313,3 +314,21 @@ def test_estimate_sell_received_cash( ) assert usdc_received / 1e18 == pytest.approx(14159.565580618213) assert block_number > 0 + + +def test_get_onchain_price(web3, weth_usdc_uniswap_pool: str): + """Test get onchain price of a pool.""" + + price = get_onchain_price( + web3, + weth_usdc_uniswap_pool, + ) + + assert price == pytest.approx(Decimal(1699.9057541866793)) + + reverse_price = get_onchain_price( + web3, + weth_usdc_uniswap_pool, + reverse_token_order=True, + ) + assert reverse_price == pytest.approx(Decimal(0.00058826790693372))