From f6980db5c61f1fb99933b2739f46dcd31bf42d00 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Sat, 13 Nov 2021 11:01:02 +0200 Subject: [PATCH 1/4] feat: improved estimate_price_impact helper function --- uniswap/assets/uniswap-v3/factory.abi | 236 ++++++ uniswap/assets/uniswap-v3/pool.abi | 988 ++++++++++++++++++++++++++ uniswap/uniswap.py | 64 +- 3 files changed, 1283 insertions(+), 5 deletions(-) create mode 100644 uniswap/assets/uniswap-v3/factory.abi create mode 100644 uniswap/assets/uniswap-v3/pool.abi diff --git a/uniswap/assets/uniswap-v3/factory.abi b/uniswap/assets/uniswap-v3/factory.abi new file mode 100644 index 00000000..63f8e7a5 --- /dev/null +++ b/uniswap/assets/uniswap-v3/factory.abi @@ -0,0 +1,236 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "FeeAmountEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "enableFeeAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "name": "feeAmountTickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "parameters", + "outputs": [ + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/uniswap/assets/uniswap-v3/pool.abi b/uniswap/assets/uniswap-v3/pool.abi new file mode 100644 index 00000000..49cc338e --- /dev/null +++ b/uniswap/assets/uniswap-v3/pool.abi @@ -0,0 +1,988 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "Collect", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "name": "CollectProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "paid1", + "type": "uint256" + } + ], + "name": "Flash", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextOld", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "observationCardinalityNextNew", + "type": "uint16" + } + ], + "name": "IncreaseObservationCardinalityNext", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1Old", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol0New", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "feeProtocol1New", + "type": "uint8" + } + ], + "name": "SetFeeProtocol", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "amount1", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collect", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "amount0Requested", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1Requested", + "type": "uint128" + } + ], + "name": "collectProtocol", + "outputs": [ + { + "internalType": "uint128", + "name": "amount0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "amount1", + "type": "uint128" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "fee", + "outputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal0X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeGrowthGlobal1X128", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "flash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + } + ], + "name": "increaseObservationCardinalityNext", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "liquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxLiquidityPerTick", + "outputs": [ + { + "internalType": "uint128", + "name": "", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "uint128", + "name": "amount", + "type": "uint128" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "observations", + "outputs": [ + { + "internalType": "uint32", + "name": "blockTimestamp", + "type": "uint32" + }, + { + "internalType": "int56", + "name": "tickCumulative", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityCumulativeX128", + "type": "uint160" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint32[]", + "name": "secondsAgos", + "type": "uint32[]" + } + ], + "name": "observe", + "outputs": [ + { + "internalType": "int56[]", + "name": "tickCumulatives", + "type": "int56[]" + }, + { + "internalType": "uint160[]", + "name": "secondsPerLiquidityCumulativeX128s", + "type": "uint160[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "positions", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "tokensOwed0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "tokensOwed1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFees", + "outputs": [ + { + "internalType": "uint128", + "name": "token0", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "token1", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "feeProtocol0", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "feeProtocol1", + "type": "uint8" + } + ], + "name": "setFeeProtocol", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint16", + "name": "observationIndex", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinality", + "type": "uint16" + }, + { + "internalType": "uint16", + "name": "observationCardinalityNext", + "type": "uint16" + }, + { + "internalType": "uint8", + "name": "feeProtocol", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "unlocked", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "snapshotCumulativesInside", + "outputs": [ + { + "internalType": "int56", + "name": "tickCumulativeInside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityInsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsInside", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "int256", + "name": "amount0", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int16", + "name": "", + "type": "int16" + } + ], + "name": "tickBitmap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "name": "ticks", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidityGross", + "type": "uint128" + }, + { + "internalType": "int128", + "name": "liquidityNet", + "type": "int128" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthOutside1X128", + "type": "uint256" + }, + { + "internalType": "int56", + "name": "tickCumulativeOutside", + "type": "int56" + }, + { + "internalType": "uint160", + "name": "secondsPerLiquidityOutsideX128", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "secondsOutside", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "initialized", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/uniswap/uniswap.py b/uniswap/uniswap.py index 6c54b70a..5d33fe0d 100644 --- a/uniswap/uniswap.py +++ b/uniswap/uniswap.py @@ -136,6 +136,12 @@ def __init__( ) elif self.version == 3: # https://github.com/Uniswap/uniswap-v3-periphery/blob/main/deploys.md + factory_contract_address = _str_to_addr( + "0x1F98431c8aD98523631AE4a59f267346ea31F984" + ) + self.factory_contract = _load_contract( + self.w3, abi_name="uniswap-v3/factory", address=factory_contract_address + ) quoter_addr = _str_to_addr("0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6") self.router_address = _str_to_addr( "0xE592427A0AEce92De3Edee1F18E0157C05861564" @@ -1209,6 +1215,51 @@ def get_weth_address(self) -> ChecksumAddress: address = self.router.functions.WETH9().call() return address + def get_raw_price( + self, + token_in: AddressLike, + token_out: AddressLike, + fee: int = 3000) -> float: + + if token_in == ETH_ADDRESS: + token_in = self.get_weth_address() + if token_out == ETH_ADDRESS: + token_out = self.get_weth_address() + + if self.version == 2: + params = [self.w3.toChecksumAddress(token_in), self.w3.toChecksumAddress(token_out)] + pair_token = self.factory_contract.functions.getPair(*params).call() + token_in_erc20 = self.erc20_contract(self.w3.toChecksumAddress(token_in)) + token_in_balance = int(token_in_erc20.functions.balanceOf(self.w3.toChecksumAddress(pair_token)).call()) + token_in_decimals = self.get_token(token_in).decimals + token_in_balance = token_in_balance/(10**token_in_decimals) + + token_out_erc20 = self.erc20_contract(self.w3.toChecksumAddress(token_out)) + token_out_balance = int(token_out_erc20.functions.balanceOf(self.w3.toChecksumAddress(pair_token)).call()) + token_out_decimals = self.get_token(token_out).decimals + token_out_balance = token_out_balance/(10**token_out_decimals) + + raw_price = token_out_balance/token_in_balance + else: + params = [self.w3.toChecksumAddress(token_in), self.w3.toChecksumAddress(token_out), fee] + pool_address = self.factory_contract.functions.getPool(*params).call() + pool_contract = _load_contract( + self.w3, abi_name="uniswap-v3/pool", address=pool_address + ) + t0 = pool_contract.functions.token0().call() + t1 = pool_contract.functions.token1().call() + if t1.lower() == token_in.lower(): + den0 = self.get_token(token_in).decimals + den1 = self.get_token(token_out).decimals + else: + den0 = self.get_token(token_out).decimals + den1 = self.get_token(token_in).decimals + sqrtPriceX96 = pool_contract.functions.slot0().call()[0] + raw_price = (sqrtPriceX96*sqrtPriceX96*10**den1>>(96*2))/(10**den0) + if t1.lower() == token_in.lower(): + raw_price = 1/raw_price + return str(raw_price) + def estimate_price_impact( self, token_in: AddressLike, @@ -1224,15 +1275,18 @@ def estimate_price_impact( See ``examples/price_impact.py`` for an example which uses this. """ - amount_small = 10 ** 2 - cost_small = self.get_price_input( - token_in, token_out, amount_small, fee=fee, route=route - ) + try: + price_small = self.get_raw_price( + token_in, token_out, fee=fee, + ) + except: + return 1 + if price_small == 0: + return 1 cost_amount = self.get_price_input( token_in, token_out, amount_in, fee=fee, route=route ) - price_small = cost_small / amount_small price_amount = cost_amount / amount_in return (price_small - price_amount) / price_small From b514561950bc6bbefafd6a7ff9c95b838a732db9 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Sat, 13 Nov 2021 12:05:52 +0200 Subject: [PATCH 2/4] feat: improved estimate_price_impact helper function --- uniswap/uniswap.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/uniswap/uniswap.py b/uniswap/uniswap.py index 5d33fe0d..f81c5d13 100644 --- a/uniswap/uniswap.py +++ b/uniswap/uniswap.py @@ -1229,12 +1229,12 @@ def get_raw_price( if self.version == 2: params = [self.w3.toChecksumAddress(token_in), self.w3.toChecksumAddress(token_out)] pair_token = self.factory_contract.functions.getPair(*params).call() - token_in_erc20 = self.erc20_contract(self.w3.toChecksumAddress(token_in)) + token_in_erc20 = _load_contract_erc20(self.w3, self.w3.toChecksumAddress(token_in)) token_in_balance = int(token_in_erc20.functions.balanceOf(self.w3.toChecksumAddress(pair_token)).call()) token_in_decimals = self.get_token(token_in).decimals token_in_balance = token_in_balance/(10**token_in_decimals) - token_out_erc20 = self.erc20_contract(self.w3.toChecksumAddress(token_out)) + token_out_erc20 = _load_contract_erc20(self.w3, self.w3.toChecksumAddress(token_out)) token_out_balance = int(token_out_erc20.functions.balanceOf(self.w3.toChecksumAddress(pair_token)).call()) token_out_decimals = self.get_token(token_out).decimals token_out_balance = token_out_balance/(10**token_out_decimals) @@ -1258,7 +1258,7 @@ def get_raw_price( raw_price = (sqrtPriceX96*sqrtPriceX96*10**den1>>(96*2))/(10**den0) if t1.lower() == token_in.lower(): raw_price = 1/raw_price - return str(raw_price) + return raw_price def estimate_price_impact( self, @@ -1283,11 +1283,13 @@ def estimate_price_impact( return 1 if price_small == 0: return 1 - cost_amount = self.get_price_input( - token_in, token_out, amount_in, fee=fee, route=route - ) - - price_amount = cost_amount / amount_in + try: + cost_amount = self.get_price_input( + token_in, token_out, amount_in, fee=fee, route=route + ) + except: + return 1 + price_amount = (cost_amount / (amount_in/(10**self.get_token(token_in).decimals)))/10**self.get_token(token_out).decimals return (price_small - price_amount) / price_small From 18fb620f83b950a6621e63fcc5c54bd6a2fd7339 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Thu, 18 Nov 2021 12:00:30 +0200 Subject: [PATCH 3/4] feat: improved estimate_price_impact helper function; added docstrings --- uniswap/uniswap.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/uniswap/uniswap.py b/uniswap/uniswap.py index f81c5d13..326905b0 100644 --- a/uniswap/uniswap.py +++ b/uniswap/uniswap.py @@ -7,6 +7,7 @@ from web3 import Web3 from web3.eth import Contract from web3.contract import ContractFunction +from web3.exceptions import BadFunctionCallOutput, ContractLogicError from web3.types import ( TxParams, Wei, @@ -1215,12 +1216,15 @@ def get_weth_address(self) -> ChecksumAddress: address = self.router.functions.WETH9().call() return address + @supports([2,3]) def get_raw_price( self, token_in: AddressLike, token_out: AddressLike, fee: int = 3000) -> float: - + """Returns current price for pair of tokens [token_in, token_out] regrading liquidity that is being locked in the pool""" + """Parameter `fee` is required for V3 only, can be omitted for V2""" + """Requires pair [token_in, token_out] having direct pool""" if token_in == ETH_ADDRESS: token_in = self.get_weth_address() if token_out == ETH_ADDRESS: @@ -1279,15 +1283,21 @@ def estimate_price_impact( price_small = self.get_raw_price( token_in, token_out, fee=fee, ) - except: + except (ArithmeticError, BadFunctionCallOutput): + #ArithmeticError is raised when `token_in` amount in the pool equals 0. + #BadFunctionCallOutput is raised when the pool's contract for given `(token_in, token_out, fee)` hasn't been deployed return 1 + if price_small == 0: + #Occures when `token_out` amount in the pool equals 0 return 1 try: cost_amount = self.get_price_input( token_in, token_out, amount_in, fee=fee, route=route ) - except: + except ContractLogicError: + #ContractLogicError is raised when the pool's contract for given `(token_in, token_out, fee)` hasn't been deployed. + #As `get_price_input()` uses UniswapV3Quoter for getting prices, that contract raises such exception in this situation. return 1 price_amount = (cost_amount / (amount_in/(10**self.get_token(token_in).decimals)))/10**self.get_token(token_out).decimals From 91e100e1c5aeafdfd7967c4a5a550457331516fd Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Thu, 18 Nov 2021 12:49:11 +0200 Subject: [PATCH 4/4] feat: improved estimate_price_impact helper function; added docstrings; codestyle fixed --- uniswap/uniswap.py | 75 ++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/uniswap/uniswap.py b/uniswap/uniswap.py index 326905b0..a8f5b690 100644 --- a/uniswap/uniswap.py +++ b/uniswap/uniswap.py @@ -1216,12 +1216,10 @@ def get_weth_address(self) -> ChecksumAddress: address = self.router.functions.WETH9().call() return address - @supports([2,3]) + @supports([2, 3]) def get_raw_price( - self, - token_in: AddressLike, - token_out: AddressLike, - fee: int = 3000) -> float: + self, token_in: AddressLike, token_out: AddressLike, fee: int = 3000 + ) -> float: """Returns current price for pair of tokens [token_in, token_out] regrading liquidity that is being locked in the pool""" """Parameter `fee` is required for V3 only, can be omitted for V2""" """Requires pair [token_in, token_out] having direct pool""" @@ -1231,25 +1229,44 @@ def get_raw_price( token_out = self.get_weth_address() if self.version == 2: - params = [self.w3.toChecksumAddress(token_in), self.w3.toChecksumAddress(token_out)] + params = [ + self.w3.toChecksumAddress(token_in), + self.w3.toChecksumAddress(token_out), + ] pair_token = self.factory_contract.functions.getPair(*params).call() - token_in_erc20 = _load_contract_erc20(self.w3, self.w3.toChecksumAddress(token_in)) - token_in_balance = int(token_in_erc20.functions.balanceOf(self.w3.toChecksumAddress(pair_token)).call()) + token_in_erc20 = _load_contract_erc20( + self.w3, self.w3.toChecksumAddress(token_in) + ) + token_in_balance = int( + token_in_erc20.functions.balanceOf( + self.w3.toChecksumAddress(pair_token) + ).call() + ) token_in_decimals = self.get_token(token_in).decimals - token_in_balance = token_in_balance/(10**token_in_decimals) + token_in_balance = token_in_balance / (10 ** token_in_decimals) - token_out_erc20 = _load_contract_erc20(self.w3, self.w3.toChecksumAddress(token_out)) - token_out_balance = int(token_out_erc20.functions.balanceOf(self.w3.toChecksumAddress(pair_token)).call()) + token_out_erc20 = _load_contract_erc20( + self.w3, self.w3.toChecksumAddress(token_out) + ) + token_out_balance = int( + token_out_erc20.functions.balanceOf( + self.w3.toChecksumAddress(pair_token) + ).call() + ) token_out_decimals = self.get_token(token_out).decimals - token_out_balance = token_out_balance/(10**token_out_decimals) + token_out_balance = token_out_balance / (10 ** token_out_decimals) - raw_price = token_out_balance/token_in_balance + raw_price = token_out_balance / token_in_balance else: - params = [self.w3.toChecksumAddress(token_in), self.w3.toChecksumAddress(token_out), fee] + params = [ + self.w3.toChecksumAddress(token_in), + self.w3.toChecksumAddress(token_out), + fee, + ] pool_address = self.factory_contract.functions.getPool(*params).call() pool_contract = _load_contract( - self.w3, abi_name="uniswap-v3/pool", address=pool_address - ) + self.w3, abi_name="uniswap-v3/pool", address=pool_address + ) t0 = pool_contract.functions.token0().call() t1 = pool_contract.functions.token1().call() if t1.lower() == token_in.lower(): @@ -1259,9 +1276,11 @@ def get_raw_price( den0 = self.get_token(token_out).decimals den1 = self.get_token(token_in).decimals sqrtPriceX96 = pool_contract.functions.slot0().call()[0] - raw_price = (sqrtPriceX96*sqrtPriceX96*10**den1>>(96*2))/(10**den0) + raw_price = (sqrtPriceX96 * sqrtPriceX96 * 10 ** den1 >> (96 * 2)) / ( + 10 ** den0 + ) if t1.lower() == token_in.lower(): - raw_price = 1/raw_price + raw_price = 1 / raw_price return raw_price def estimate_price_impact( @@ -1281,25 +1300,29 @@ def estimate_price_impact( """ try: price_small = self.get_raw_price( - token_in, token_out, fee=fee, + token_in, + token_out, + fee=fee, ) except (ArithmeticError, BadFunctionCallOutput): - #ArithmeticError is raised when `token_in` amount in the pool equals 0. - #BadFunctionCallOutput is raised when the pool's contract for given `(token_in, token_out, fee)` hasn't been deployed + # ArithmeticError is raised when `token_in` amount in the pool equals 0. + # BadFunctionCallOutput is raised when the pool's contract for given `(token_in, token_out, fee)` hasn't been deployed return 1 - + if price_small == 0: - #Occures when `token_out` amount in the pool equals 0 + # Occures when `token_out` amount in the pool equals 0 return 1 try: cost_amount = self.get_price_input( token_in, token_out, amount_in, fee=fee, route=route ) except ContractLogicError: - #ContractLogicError is raised when the pool's contract for given `(token_in, token_out, fee)` hasn't been deployed. - #As `get_price_input()` uses UniswapV3Quoter for getting prices, that contract raises such exception in this situation. + # ContractLogicError is raised when the pool's contract for given `(token_in, token_out, fee)` hasn't been deployed. + # As `get_price_input()` uses UniswapV3Quoter for getting prices, that contract raises such exception in this situation. return 1 - price_amount = (cost_amount / (amount_in/(10**self.get_token(token_in).decimals)))/10**self.get_token(token_out).decimals + price_amount = ( + cost_amount / (amount_in / (10 ** self.get_token(token_in).decimals)) + ) / 10 ** self.get_token(token_out).decimals return (price_small - price_amount) / price_small