├── FixedPoint128.py ├── FixedPoint96.py ├── UnsafeMath.py ├── README.md ├── __init__.py ├── YulOperations.py ├── FullMath.py ├── Helpers.py ├── BitMath.py ├── TickBitmap.py ├── .gitignore ├── SwapMath.py ├── SqrtPriceMath.py └── TickMath.py /FixedPoint128.py: -------------------------------------------------------------------------------- 1 | Q128 = 0x100000000000000000000000000000000 2 | -------------------------------------------------------------------------------- /FixedPoint96.py: -------------------------------------------------------------------------------- 1 | Q96 = 0x1000000000000000000000000 2 | RESOLUTION = 96 3 | -------------------------------------------------------------------------------- /UnsafeMath.py: -------------------------------------------------------------------------------- 1 | from . import YulOperations as yul 2 | 3 | 4 | def divRoundingUp(x, y): 5 | return yul.add(yul.div(x, y), yul.gt(yul.mod(x, y), 0)) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # univ3py 2 | This repo is abandoned, all improvements are in the degenbot repo going forward. These libraries are now located at https://github.com/BowTiedDevil/degenbot/tree/main/uniswap/v3/libraries 3 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .BitMath import * 2 | from .FullMath import * 3 | from .SqrtPriceMath import * 4 | from .SwapMath import * 5 | from .TickBitmap import * 6 | from .TickMath import * 7 | from .UnsafeMath import * 8 | from . import YulOperations as yul 9 | -------------------------------------------------------------------------------- /YulOperations.py: -------------------------------------------------------------------------------- 1 | def gt(x, y): 2 | return 1 if x > y else 0 3 | 4 | 5 | def lt(x, y): 6 | return 1 if x < y else 0 7 | 8 | 9 | def mod(x, y): 10 | return 0 if y == 0 else x % y 11 | 12 | 13 | def mul(x, y): 14 | return x * y 15 | 16 | 17 | def mulmod(x, y, m): 18 | return 0 if m == 0 else (x * y) % m 19 | 20 | 21 | def shl(x, y): 22 | return y << x 23 | 24 | 25 | def shr(x, y): 26 | return y >> x 27 | 28 | 29 | def _or(x, y): 30 | return x | y 31 | 32 | 33 | def _not(x): 34 | return ~x 35 | 36 | 37 | def add(x, y): 38 | return x + y 39 | 40 | 41 | def sub(x, y): 42 | return x - y 43 | 44 | 45 | def div(x, y): 46 | return 0 if y == 0 else x // y 47 | -------------------------------------------------------------------------------- /FullMath.py: -------------------------------------------------------------------------------- 1 | from . import YulOperations as yul 2 | 3 | 4 | class FullMathException(Exception): 5 | pass 6 | 7 | 8 | def mulDiv(a, b, denominator): 9 | """ 10 | The Solidity implementation is designed to calculate a * b / d without risk of overflowing 11 | the intermediate result (maximum of 2**256-1). Python does not have this limitation, 12 | so simply check for exceptional conditions then return the value 13 | """ 14 | assert denominator, FullMathException("DIVISION BY ZERO") 15 | result = (a * b) // denominator 16 | assert result <= 2**256 - 1, FullMathException("uint256 overflow") 17 | return result 18 | 19 | 20 | def mulDivRoundingUp(a, b, denominator): 21 | result = mulDiv(a, b, denominator) 22 | if yul.mulmod(a, b, denominator) > 0: 23 | assert result < 2**256 - 1, "FAIL!" 24 | result += 1 25 | return result 26 | -------------------------------------------------------------------------------- /Helpers.py: -------------------------------------------------------------------------------- 1 | # generic integer conversion to mimic Solidity's inline typecasting for int/uint types 2 | def _int(x): 3 | return x 4 | 5 | 6 | int8 = _int 7 | int16 = _int 8 | int24 = _int 9 | int32 = _int 10 | int40 = _int 11 | int48 = _int 12 | int56 = _int 13 | int64 = _int 14 | int72 = _int 15 | int80 = _int 16 | int88 = _int 17 | int96 = _int 18 | int104 = _int 19 | int112 = _int 20 | int120 = _int 21 | int128 = _int 22 | int136 = _int 23 | int144 = _int 24 | int152 = _int 25 | int160 = _int 26 | int168 = _int 27 | int176 = _int 28 | int184 = _int 29 | int192 = _int 30 | int200 = _int 31 | int208 = _int 32 | int216 = _int 33 | int224 = _int 34 | int232 = _int 35 | int240 = _int 36 | int248 = _int 37 | int256 = _int 38 | 39 | 40 | uint8 = _int 41 | uint16 = _int 42 | uint24 = _int 43 | uint32 = _int 44 | uint40 = _int 45 | uint48 = _int 46 | uint56 = _int 47 | uint64 = _int 48 | uint72 = _int 49 | uint80 = _int 50 | uint88 = _int 51 | uint96 = _int 52 | uint104 = _int 53 | uint112 = _int 54 | uint120 = _int 55 | uint128 = _int 56 | uint136 = _int 57 | uint144 = _int 58 | uint152 = _int 59 | uint160 = _int 60 | uint168 = _int 61 | uint176 = _int 62 | uint184 = _int 63 | uint192 = _int 64 | uint200 = _int 65 | uint208 = _int 66 | uint216 = _int 67 | uint224 = _int 68 | uint232 = _int 69 | uint240 = _int 70 | uint248 = _int 71 | uint256 = _int 72 | -------------------------------------------------------------------------------- /BitMath.py: -------------------------------------------------------------------------------- 1 | def mostSignificantBit(x: int) -> int: 2 | 3 | assert x > 0, "FAIL: x > 0" 4 | 5 | r = 0 6 | 7 | if x >= 0x100000000000000000000000000000000: 8 | x >>= 128 9 | r += 128 10 | 11 | if x >= 0x10000000000000000: 12 | x >>= 64 13 | r += 64 14 | 15 | if x >= 0x100000000: 16 | x >>= 32 17 | r += 32 18 | 19 | if x >= 0x10000: 20 | x >>= 16 21 | r += 16 22 | 23 | if x >= 0x100: 24 | x >>= 8 25 | r += 8 26 | 27 | if x >= 0x10: 28 | x >>= 4 29 | r += 4 30 | 31 | if x >= 0x4: 32 | x >>= 2 33 | r += 2 34 | 35 | if x >= 0x2: 36 | r += 1 37 | 38 | return r 39 | 40 | 41 | def leastSignificantBit(x: int) -> int: 42 | 43 | assert x > 0, "FAIL: x > 0" 44 | 45 | r = 255 46 | if x & 2**128 - 1 > 0: 47 | r -= 128 48 | else: 49 | x >>= 128 50 | 51 | if x & 2**64 - 1 > 0: 52 | r -= 64 53 | else: 54 | x >>= 64 55 | 56 | if x & 2**32 - 1 > 0: 57 | r -= 32 58 | else: 59 | x >>= 32 60 | 61 | if x & 2**16 - 1 > 0: 62 | r -= 16 63 | else: 64 | x >>= 16 65 | 66 | if x & 2**8 - 1 > 0: 67 | r -= 8 68 | else: 69 | x >>= 8 70 | 71 | if x & 0xF > 0: 72 | r -= 4 73 | else: 74 | x >>= 4 75 | 76 | if x & 0x3 > 0: 77 | r -= 2 78 | else: 79 | x >>= 2 80 | 81 | if x & 0x1 > 0: 82 | r -= 1 83 | 84 | return r 85 | -------------------------------------------------------------------------------- /TickBitmap.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from . import BitMath 3 | from .Helpers import * 4 | 5 | 6 | def position(tick: int) -> Tuple[int, int]: 7 | wordPos: int = int16(tick >> 8) 8 | bitPos: int = uint8(tick % 256) 9 | return (wordPos, bitPos) 10 | 11 | 12 | def nextInitializedTickWithinOneWord( 13 | tick_mapping: dict, 14 | tick: int, 15 | tickSpacing: int, 16 | lte: bool, 17 | ) -> Tuple[int, bool]: 18 | 19 | compressed: int = tick // tickSpacing 20 | if tick < 0 and tick % tickSpacing != 0: 21 | compressed -= 1 # round towards negative infinity 22 | 23 | if lte: 24 | wordPos: int 25 | bitPos: int 26 | wordPos, bitPos = position(compressed) 27 | # all the 1s at or to the right of the current bitPos 28 | mask: int = (1 << bitPos) - 1 + (1 << bitPos) 29 | masked: int = tick_mapping[wordPos] & mask 30 | 31 | # if there are no initialized ticks to the right of or at the current tick, return rightmost in the word 32 | initialized_status = masked != 0 33 | # overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick 34 | next_tick = ( 35 | (compressed - int24(bitPos - BitMath.mostSignificantBit(masked))) 36 | * tickSpacing 37 | if initialized_status 38 | else (compressed - int24(bitPos)) * tickSpacing 39 | ) 40 | else: 41 | # start from the word of the next tick, since the current tick state doesn't matter 42 | wordPos, bitPos = position(compressed + 1) 43 | # all the 1s at or to the left of the bitPos 44 | mask: int = ~((1 << bitPos) - 1) 45 | masked: int = tick_mapping[wordPos] & mask 46 | 47 | # if there are no initialized ticks to the left of the current tick, return leftmost in the word 48 | initialized_status = masked != 0 49 | # overflow/underflow is possible, but prevented externally by limiting both tickSpacing and tick 50 | next_tick = ( 51 | ( 52 | compressed 53 | + 1 54 | + int24(BitMath.leastSignificantBit(masked) - bitPos) 55 | ) 56 | * tickSpacing 57 | if initialized_status 58 | else (compressed + 1 + int24(type(uint8).max - bitPos)) 59 | * tickSpacing 60 | ) 61 | 62 | return next_tick, initialized_status 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Pyenv 163 | .python-version 164 | 165 | -------------------------------------------------------------------------------- /SwapMath.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from . import SqrtPriceMath, FullMath 3 | from .Helpers import uint256 4 | 5 | 6 | def computeSwapStep( 7 | sqrtRatioCurrentX96: int, 8 | sqrtRatioTargetX96: int, 9 | liquidity: int, 10 | amountRemaining: int, 11 | feePips: int, 12 | ) -> Tuple[int, int, int, int]: 13 | 14 | zeroForOne: bool = sqrtRatioCurrentX96 >= sqrtRatioTargetX96 15 | exactIn: bool = amountRemaining >= 0 16 | 17 | if exactIn: 18 | amountRemainingLessFee: int = FullMath.mulDiv( 19 | uint256(amountRemaining), 10**6 - feePips, 10**6 20 | ) 21 | amountIn = ( 22 | SqrtPriceMath.getAmount0Delta( 23 | sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, True 24 | ) 25 | if zeroForOne 26 | else SqrtPriceMath.getAmount1Delta( 27 | sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, True 28 | ) 29 | ) 30 | if amountRemainingLessFee >= amountIn: 31 | sqrtRatioNextX96 = sqrtRatioTargetX96 32 | else: 33 | sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromInput( 34 | sqrtRatioCurrentX96, 35 | liquidity, 36 | amountRemainingLessFee, 37 | zeroForOne, 38 | ) 39 | else: 40 | amountOut = ( 41 | SqrtPriceMath.getAmount1Delta( 42 | sqrtRatioTargetX96, sqrtRatioCurrentX96, liquidity, False 43 | ) 44 | if zeroForOne 45 | else SqrtPriceMath.getAmount0Delta( 46 | sqrtRatioCurrentX96, sqrtRatioTargetX96, liquidity, False 47 | ) 48 | ) 49 | if uint256(-amountRemaining) >= amountOut: 50 | sqrtRatioNextX96 = sqrtRatioTargetX96 51 | else: 52 | sqrtRatioNextX96 = SqrtPriceMath.getNextSqrtPriceFromOutput( 53 | sqrtRatioCurrentX96, 54 | liquidity, 55 | uint256(-amountRemaining), 56 | zeroForOne, 57 | ) 58 | 59 | max: bool = sqrtRatioTargetX96 == sqrtRatioNextX96 60 | # get the input/output amounts 61 | if zeroForOne: 62 | amountIn = ( 63 | amountIn 64 | if (max and exactIn) 65 | else SqrtPriceMath.getAmount0Delta( 66 | sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, True 67 | ) 68 | ) 69 | amountOut = ( 70 | amountOut 71 | if (max and not exactIn) 72 | else SqrtPriceMath.getAmount1Delta( 73 | sqrtRatioNextX96, sqrtRatioCurrentX96, liquidity, False 74 | ) 75 | ) 76 | else: 77 | amountIn = ( 78 | amountIn 79 | if (max and exactIn) 80 | else SqrtPriceMath.getAmount1Delta( 81 | sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, True 82 | ) 83 | ) 84 | amountOut = ( 85 | amountOut 86 | if (max and not exactIn) 87 | else SqrtPriceMath.getAmount0Delta( 88 | sqrtRatioCurrentX96, sqrtRatioNextX96, liquidity, False 89 | ) 90 | ) 91 | 92 | # cap the output amount to not exceed the remaining output amount 93 | if not exactIn and (amountOut > uint256(-amountRemaining)): 94 | amountOut = uint256(-amountRemaining) 95 | 96 | if exactIn and (sqrtRatioNextX96 != sqrtRatioTargetX96): 97 | # we didn't reach the target, so take the remainder of the maximum input as fee 98 | feeAmount = uint256(amountRemaining) - amountIn 99 | else: 100 | feeAmount = FullMath.mulDivRoundingUp( 101 | amountIn, feePips, 10**6 - feePips 102 | ) 103 | 104 | return ( 105 | sqrtRatioNextX96, 106 | amountIn, 107 | amountOut, 108 | feeAmount, 109 | ) 110 | -------------------------------------------------------------------------------- /SqrtPriceMath.py: -------------------------------------------------------------------------------- 1 | from . import FullMath, FixedPoint96, UnsafeMath 2 | from .Helpers import uint128, uint256 3 | 4 | 5 | def getNextSqrtPriceFromAmount0RoundingUp( 6 | sqrtPX96: int, liquidity: int, amount: int, add: bool 7 | ) -> int: 8 | # we short circuit amount == 0 because the result is otherwise not guaranteed to equal the input price 9 | if amount == 0: 10 | return sqrtPX96 11 | 12 | numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION 13 | 14 | if add: 15 | product = amount * sqrtPX96 16 | if product // amount == sqrtPX96: 17 | denominator = numerator1 + product 18 | if denominator >= numerator1: 19 | # always fits in 160 bits 20 | return FullMath.mulDivRoundingUp( 21 | numerator1, sqrtPX96, denominator 22 | ) 23 | 24 | return UnsafeMath.divRoundingUp( 25 | numerator1, numerator1 // sqrtPX96 + amount 26 | ) 27 | else: 28 | product = amount * sqrtPX96 29 | # if the product overflows, we know the denominator underflows 30 | # in addition, we must check that the denominator does not underflow 31 | assert product // amount == sqrtPX96 and numerator1 > product, "FAIL!" 32 | denominator = numerator1 - product 33 | return FullMath.mulDivRoundingUp(numerator1, sqrtPX96, denominator) 34 | 35 | 36 | def getNextSqrtPriceFromAmount1RoundingDown( 37 | sqrtPX96: int, 38 | liquidity: int, 39 | amount: int, 40 | add: bool, 41 | ) -> int: 42 | 43 | if add: 44 | quotient = ( 45 | (amount << FixedPoint96.RESOLUTION) // liquidity 46 | if amount <= 2**160 - 1 47 | else FullMath.mulDiv(amount, FixedPoint96.Q96, liquidity) 48 | ) 49 | return uint256(sqrtPX96) + quotient 50 | else: 51 | quotient = ( 52 | UnsafeMath.divRoundingUp( 53 | amount << FixedPoint96.RESOLUTION, liquidity 54 | ) 55 | if amount <= (2**160) - 1 56 | else FullMath.mulDivRoundingUp(amount, FixedPoint96.Q96, liquidity) 57 | ) 58 | 59 | assert sqrtPX96 > quotient, "FAIL!" 60 | # always fits 160 bits 61 | return sqrtPX96 - quotient 62 | 63 | 64 | def getNextSqrtPriceFromInput( 65 | sqrtPX96: int, 66 | liquidity: int, 67 | amountIn: int, 68 | zeroForOne: bool, 69 | ): 70 | assert sqrtPX96 > 0, "FAIL!" 71 | assert liquidity > 0, "FAIL!" 72 | 73 | # round to make sure that we don't pass the target price 74 | return ( 75 | getNextSqrtPriceFromAmount0RoundingUp( 76 | sqrtPX96, liquidity, amountIn, True 77 | ) 78 | if zeroForOne 79 | else getNextSqrtPriceFromAmount1RoundingDown( 80 | sqrtPX96, liquidity, amountIn, True 81 | ) 82 | ) 83 | 84 | 85 | def getNextSqrtPriceFromOutput( 86 | sqrtPX96: int, 87 | liquidity: int, 88 | amountOut: int, 89 | zeroForOne: bool, 90 | ): 91 | assert sqrtPX96 > 0, "FAIL!" 92 | assert liquidity > 0, "FAIL!" 93 | 94 | # round to make sure that we pass the target price 95 | return ( 96 | getNextSqrtPriceFromAmount1RoundingDown( 97 | sqrtPX96, liquidity, amountOut, False 98 | ) 99 | if zeroForOne 100 | else getNextSqrtPriceFromAmount0RoundingUp( 101 | sqrtPX96, liquidity, amountOut, False 102 | ) 103 | ) 104 | 105 | 106 | def getAmount0Delta( 107 | sqrtRatioAX96: int, 108 | sqrtRatioBX96: int, 109 | liquidity: int, 110 | roundUp: bool = None, 111 | ) -> int: 112 | 113 | if roundUp is not None: 114 | if sqrtRatioAX96 > sqrtRatioBX96: 115 | (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96) 116 | 117 | numerator1 = uint256(liquidity) << FixedPoint96.RESOLUTION 118 | numerator2 = sqrtRatioBX96 - sqrtRatioAX96 119 | 120 | assert sqrtRatioAX96 > 0, "FAIL!" 121 | 122 | return ( 123 | UnsafeMath.divRoundingUp( 124 | FullMath.mulDivRoundingUp( 125 | numerator1, numerator2, sqrtRatioBX96 126 | ), 127 | sqrtRatioAX96, 128 | ) 129 | if roundUp 130 | else FullMath.mulDiv(numerator1, numerator2, sqrtRatioBX96) 131 | // sqrtRatioAX96 132 | ) 133 | 134 | else: 135 | return ( 136 | -getAmount0Delta( 137 | sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), False 138 | ) 139 | if liquidity < 0 140 | else getAmount0Delta( 141 | sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), True 142 | ) 143 | ) 144 | 145 | 146 | def getAmount1Delta( 147 | sqrtRatioAX96: int, 148 | sqrtRatioBX96: int, 149 | liquidity: int, 150 | roundUp: bool = None, 151 | ) -> int: 152 | 153 | if roundUp is not None: 154 | if sqrtRatioAX96 > sqrtRatioBX96: 155 | (sqrtRatioAX96, sqrtRatioBX96) = (sqrtRatioBX96, sqrtRatioAX96) 156 | 157 | return ( 158 | FullMath.mulDivRoundingUp( 159 | liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96 160 | ) 161 | if roundUp 162 | else FullMath.mulDiv( 163 | liquidity, sqrtRatioBX96 - sqrtRatioAX96, FixedPoint96.Q96 164 | ) 165 | ) 166 | 167 | else: 168 | return ( 169 | -getAmount1Delta( 170 | sqrtRatioAX96, sqrtRatioBX96, uint128(-liquidity), False 171 | ) 172 | if liquidity < 0 173 | else getAmount1Delta( 174 | sqrtRatioAX96, sqrtRatioBX96, uint128(liquidity), True 175 | ) 176 | ) 177 | -------------------------------------------------------------------------------- /TickMath.py: -------------------------------------------------------------------------------- 1 | from .Helpers import int24, int256, uint160, uint256 2 | from . import YulOperations as yul 3 | 4 | MIN_TICK = -887272 5 | MAX_TICK = -MIN_TICK 6 | MIN_SQRT_RATIO = 4295128739 7 | MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342 8 | 9 | 10 | def getSqrtRatioAtTick(tick: int) -> int: 11 | 12 | absTick = uint256(-int256(tick)) if tick < 0 else uint256(int256(tick)) 13 | assert absTick <= uint256(MAX_TICK), "T" 14 | 15 | ratio = ( 16 | 0xFFFCB933BD6FAD37AA2D162D1A594001 17 | if (absTick & 0x1 != 0) 18 | else 0x100000000000000000000000000000000 19 | ) 20 | 21 | if absTick & 0x2 != 0: 22 | ratio = (ratio * 0xFFF97272373D413259A46990580E213A) >> 128 23 | if absTick & 0x4 != 0: 24 | ratio = (ratio * 0xFFF2E50F5F656932EF12357CF3C7FDCC) >> 128 25 | if absTick & 0x8 != 0: 26 | ratio = (ratio * 0xFFE5CACA7E10E4E61C3624EAA0941CD0) >> 128 27 | if absTick & 0x10 != 0: 28 | ratio = (ratio * 0xFFCB9843D60F6159C9DB58835C926644) >> 128 29 | if absTick & 0x20 != 0: 30 | ratio = (ratio * 0xFF973B41FA98C081472E6896DFB254C0) >> 128 31 | if absTick & 0x40 != 0: 32 | ratio = (ratio * 0xFF2EA16466C96A3843EC78B326B52861) >> 128 33 | if absTick & 0x80 != 0: 34 | ratio = (ratio * 0xFE5DEE046A99A2A811C461F1969C3053) >> 128 35 | if absTick & 0x100 != 0: 36 | ratio = (ratio * 0xFCBE86C7900A88AEDCFFC83B479AA3A4) >> 128 37 | if absTick & 0x200 != 0: 38 | ratio = (ratio * 0xF987A7253AC413176F2B074CF7815E54) >> 128 39 | if absTick & 0x400 != 0: 40 | ratio = (ratio * 0xF3392B0822B70005940C7A398E4B70F3) >> 128 41 | if absTick & 0x800 != 0: 42 | ratio = (ratio * 0xE7159475A2C29B7443B29C7FA6E889D9) >> 128 43 | if absTick & 0x1000 != 0: 44 | ratio = (ratio * 0xD097F3BDFD2022B8845AD8F792AA5825) >> 128 45 | if absTick & 0x2000 != 0: 46 | ratio = (ratio * 0xA9F746462D870FDF8A65DC1F90E061E5) >> 128 47 | if absTick & 0x4000 != 0: 48 | ratio = (ratio * 0x70D869A156D2A1B890BB3DF62BAF32F7) >> 128 49 | if absTick & 0x8000 != 0: 50 | ratio = (ratio * 0x31BE135F97D08FD981231505542FCFA6) >> 128 51 | if absTick & 0x10000 != 0: 52 | ratio = (ratio * 0x9AA508B5B7A84E1C677DE54F3E99BC9) >> 128 53 | if absTick & 0x20000 != 0: 54 | ratio = (ratio * 0x5D6AF8DEDB81196699C329225EE604) >> 128 55 | if absTick & 0x40000 != 0: 56 | ratio = (ratio * 0x2216E584F5FA1EA926041BEDFE98) >> 128 57 | if absTick & 0x80000 != 0: 58 | ratio = (ratio * 0x48A170391F7DC42444E8FA2) >> 128 59 | 60 | if tick > 0: 61 | ratio = (2**256 - 1) // ratio 62 | 63 | # this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96 64 | # we then downcast because we know the result always fits within 160 bits due to our tick input constraint 65 | # we round up in the division so getTickAtSqrtRatio of the output price is always consistent 66 | return uint160((ratio >> 32) + (0 if (ratio % (1 << 32) == 0) else 1)) 67 | 68 | 69 | def getTickAtSqrtRatio(sqrtPriceX96: int) -> int: 70 | 71 | # second inequality must be < because the price can never reach the price at the max tick 72 | assert ( 73 | sqrtPriceX96 >= MIN_SQRT_RATIO and sqrtPriceX96 < MAX_SQRT_RATIO 74 | ), "R" 75 | ratio = uint256(sqrtPriceX96) << 32 76 | 77 | r: int = ratio 78 | msb: int = 0 79 | 80 | f = yul.shl(7, yul.gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) 81 | msb = yul._or(msb, f) 82 | r = yul.shr(f, r) 83 | 84 | f = yul.shl(7, yul.gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) 85 | msb = yul._or(msb, f) 86 | r = yul.shr(f, r) 87 | 88 | f = yul.shl(6, yul.gt(r, 0xFFFFFFFFFFFFFFFF)) 89 | msb = yul._or(msb, f) 90 | r = yul.shr(f, r) 91 | 92 | f = yul.shl(5, yul.gt(r, 0xFFFFFFFF)) 93 | msb = yul._or(msb, f) 94 | r = yul.shr(f, r) 95 | 96 | f = yul.shl(4, yul.gt(r, 0xFFFF)) 97 | msb = yul._or(msb, f) 98 | r = yul.shr(f, r) 99 | 100 | f = yul.shl(3, yul.gt(r, 0xFF)) 101 | msb = yul._or(msb, f) 102 | r = yul.shr(f, r) 103 | 104 | f = yul.shl(2, yul.gt(r, 0xF)) 105 | msb = yul._or(msb, f) 106 | r = yul.shr(f, r) 107 | 108 | f = yul.shl(1, yul.gt(r, 0x3)) 109 | msb = yul._or(msb, f) 110 | r = yul.shr(f, r) 111 | 112 | f = yul.gt(r, 0x1) 113 | msb = yul._or(msb, f) 114 | 115 | if msb >= 128: 116 | r = ratio >> (msb - 127) 117 | else: 118 | r = ratio << (127 - msb) 119 | 120 | log_2 = (int(msb) - 128) << 64 121 | 122 | r = yul.shr(127, yul.mul(r, r)) 123 | f = yul.shr(128, r) 124 | log_2 = yul._or(log_2, yul.shl(63, f)) 125 | r = yul.shr(f, r) 126 | 127 | r = yul.shr(127, yul.mul(r, r)) 128 | f = yul.shr(128, r) 129 | log_2 = yul._or(log_2, yul.shl(62, f)) 130 | r = yul.shr(f, r) 131 | 132 | r = yul.shr(127, yul.mul(r, r)) 133 | f = yul.shr(128, r) 134 | log_2 = yul._or(log_2, yul.shl(61, f)) 135 | r = yul.shr(f, r) 136 | 137 | r = yul.shr(127, yul.mul(r, r)) 138 | f = yul.shr(128, r) 139 | log_2 = yul._or(log_2, yul.shl(60, f)) 140 | r = yul.shr(f, r) 141 | 142 | r = yul.shr(127, yul.mul(r, r)) 143 | f = yul.shr(128, r) 144 | log_2 = yul._or(log_2, yul.shl(59, f)) 145 | r = yul.shr(f, r) 146 | 147 | r = yul.shr(127, yul.mul(r, r)) 148 | f = yul.shr(128, r) 149 | log_2 = yul._or(log_2, yul.shl(58, f)) 150 | r = yul.shr(f, r) 151 | 152 | r = yul.shr(127, yul.mul(r, r)) 153 | f = yul.shr(128, r) 154 | log_2 = yul._or(log_2, yul.shl(57, f)) 155 | r = yul.shr(f, r) 156 | 157 | r = yul.shr(127, yul.mul(r, r)) 158 | f = yul.shr(128, r) 159 | log_2 = yul._or(log_2, yul.shl(56, f)) 160 | r = yul.shr(f, r) 161 | 162 | r = yul.shr(127, yul.mul(r, r)) 163 | f = yul.shr(128, r) 164 | log_2 = yul._or(log_2, yul.shl(55, f)) 165 | r = yul.shr(f, r) 166 | 167 | r = yul.shr(127, yul.mul(r, r)) 168 | f = yul.shr(128, r) 169 | log_2 = yul._or(log_2, yul.shl(54, f)) 170 | r = yul.shr(f, r) 171 | 172 | r = yul.shr(127, yul.mul(r, r)) 173 | f = yul.shr(128, r) 174 | log_2 = yul._or(log_2, yul.shl(53, f)) 175 | r = yul.shr(f, r) 176 | 177 | r = yul.shr(127, yul.mul(r, r)) 178 | f = yul.shr(128, r) 179 | log_2 = yul._or(log_2, yul.shl(52, f)) 180 | r = yul.shr(f, r) 181 | 182 | r = yul.shr(127, yul.mul(r, r)) 183 | f = yul.shr(128, r) 184 | log_2 = yul._or(log_2, yul.shl(51, f)) 185 | r = yul.shr(f, r) 186 | 187 | r = yul.shr(127, yul.mul(r, r)) 188 | f = yul.shr(128, r) 189 | log_2 = yul._or(log_2, yul.shl(50, f)) 190 | 191 | log_sqrt10001 = log_2 * 255738958999603826347141 # 128.128 number 192 | 193 | tickLow = int24( 194 | (log_sqrt10001 - 3402992956809132418596140100660247210) >> 128 195 | ) 196 | tickHi = int24( 197 | (log_sqrt10001 + 291339464771989622907027621153398088495) >> 128 198 | ) 199 | 200 | tick = ( 201 | tickLow 202 | if (tickLow == tickHi) 203 | else ( 204 | tickHi if getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 else tickLow 205 | ) 206 | ) 207 | 208 | return tick 209 | --------------------------------------------------------------------------------