यह लेख बताता है कि Uniswap V3 TickMath लाइब्रेरी में getSqrtRatioAtTick() फ़ंक्शन कैसे काम करता है। getSqrtRatioAtTick() फ़ंक्शन एक tick index लेता है और उस सटीक tick पर Q64.96 q-number के रूप में square root price लौटाता है। यह फ़ंक्शन निम्नलिखित गणना करता है:
जहाँ tick index है। नीचे फ़ंक्शन का स्क्रीनशॉट दिया गया है:

यह ट्यूटोरियल मानकर चलता है कि पाठक ने square-and-multiply algorithm के हमारे विवरण को समझ लिया है, जिस पर getSqrtRatioAtTick() निर्भर करता है। हम यहाँ नियमित रूप से उस ट्यूटोरियल की अवधारणाओं का संदर्भ देते हैं, इसलिए हम सुझाव देते हैं कि पाठक पहले उस लेख की समीक्षा कर लें।
getSqrtPriceRatioAtTick overview
फ़ंक्शन निम्नलिखित कदम उठाता है:
- कोड
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));के साथ tick के absolute value की गणना करता है। - जाँच करता है कि tick, minimum और maximum tick की सीमा में आता है या नहीं और यदि यह सीमा से बाहर है तो revert कर देता है:
require(absTick <= uint256(MAX_TICK), 'T');। - square-and-multiply का उपयोग करके Q128.128 नंबर के रूप में की गणना करता है।
- यदि मूल tick पॉजिटिव था, तो की गणना करता है।
>> 32के साथ Q128.128 नंबर को Q64.96 नंबर में बदलता है।
कोड में बताए गए कदम यहाँ दिए गए हैं:

Part 1/5: Uniswap V3 absolute value tick की गणना क्यों करता है, अर्थात
फ़ंक्शन में कोड की पहली पंक्ति tick के absolute value की गणना करती है:
uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick));
Tick indices पॉजिटिव और नेगेटिव दोनों हो सकते हैं। दोनों मामलों को अलग-अलग संभालने से बचने के लिए, getSqrtRatioAtTick() का square-and-multiply हिस्सा केवल नेगेटिव ticks की गणना करता है। यदि मूल tick पॉजिटिव था, तो square-and-multiply एल्गोरिदम का परिणाम reciprocal (व्युत्क्रम) की गणना करता है।
उदाहरण के लिए, यदि मूल tick पॉजिटिव 5 है, तो एल्गोरिदम -5 के लिए tick की गणना करता है:
फिर reciprocal की गणना करता है:
ध्यान दें कि सामान्य तौर पर:
इसलिए, फ़ंक्शन पहले यह गणना करता है:
फिर, यदि tick मूल रूप से पॉजिटिव था, तो यह reciprocal लौटाकर उत्तर को उलट देता है:
यदि tick मूल रूप से नेगेटिव था, तो कोड reciprocal की गणना नहीं करता है।
Part 2/5: यह जाँचना कि tick सीमा में है या नहीं
फ़ंक्शन में कोड की दूसरी पंक्ति स्वतः स्पष्ट है:
require(absTick <= uint256(MAX_TICK), 'T');
Max tick फ़ाइल में एक constant 887272 है जिसे हमने Tick Limits पर अपने लेख में प्राप्त किया था। हमें यह जाँचने की आवश्यकता नहीं है कि tick, MIN_TICK से कम है या नहीं क्योंकि हमने tick के absolute value की गणना की है, इसलिए absTick नेगेटिव नहीं हो सकता है।
Part 3/5: square and multiply का उपयोग करके price की गणना करना
इस भाग में, हम दिखाते हैं कि फ़ंक्शन square and multiply algorithm का उपयोग कैसे कर रहा है और फ़ंक्शन में बड़े constants को कैसे प्राप्त करता है।
Price की गणना के लिए उपयोग किया जाने वाला वेरिएबल ratio है लेकिन लौटाया जाने वाला वेरिएबल sqrtPriceX96 है।
यहाँ फ़ंक्शन का प्रासंगिक हिस्सा है जो square and multiply का उपयोग करता है:
uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000;
if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128;
if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128;
if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128;
if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128;
if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128;
if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128;
if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128;
if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128;
if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128;
if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128;
if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128;
if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128;
if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128;
if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128;
if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128;
if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128;
if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128;
if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128;
if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128;
ratio एक Q128.128 है लेकिन sqrtPriceX96 एक Q64.96 है
Precision को अधिकतम करने के लिए, getSqrtRatioAtTick() square-and-multiply एल्गोरिदम को निष्पादित करते समय आंतरिक रूप से Q128.128 नंबरों का उपयोग करता है, लेकिन उत्तर Q64.96 के रूप में लौटाता है। Price का आंतरिक प्रतिनिधित्व ratio वेरिएबल है।
Square-and-multiply precomputation की समीक्षा
यह समझाने के लिए कि Uniswap V3 ने ऊपर दिखाए गए बड़े constants को कैसे प्राप्त किया, हमें पहले square and multiply एल्गोरिदम की समीक्षा करनी होगी।
Square-and-multiply एल्गोरिदम base की घातों की पूर्व-गणना पर निर्भर करता है। अब हम दिखाते हैं कि कोड में बड़े constants को बार-बार square करने से प्राप्त होते हैं।
Square-and-multiply एल्गोरिदम का उपयोग करते हुए, getSqrtRatioAtTick() पूर्व-गणना करता है:
Uniswap V3 में minimum और maximum ticks क्रमशः -887,272 और 887,272 हैं। हालाँकि, चूंकि getSqrtPriceRatioAtTick केवल नेगेटिव हिस्से की सीधे गणना करता है, और reciprocal द्वारा पॉजिटिव ticks की गणना करता है, इसलिए इसे केवल [-887,272,0] ticks की गणना करने की आवश्यकता होती है। 887,273 (887,272 प्लस 0) तक के नंबर को एनकोड करने के लिए आवश्यक बिट्स की संख्या 20 है क्योंकि । यही कारण है कि precomputed मान , ,…, तक होते हैं।
उदाहरण के तौर पर, मान लें कि हम tick -100 पर price की गणना करना चाहते हैं। हम tick -64, -32, और -4 के लिए precomputed मानों को इस प्रकार गुणा कर सकते हैं:
बड़े constant मानों को Q128.128 नंबरों के रूप में प्राप्त करना
अब हम दिखाते हैं कि Uniswap V3 ने बड़े constants को कैसे प्राप्त किया।
tick 0 का square root price
बड़ा constant 0x100000000000000000000000000000000 (पर्पल बॉक्स) Q128.128 fixed point नंबर (जो 2 << 128 के बराबर है) है।

यह tick 0, से मेल खाता है। विचार करें कि यदि tick = 0 है, तो लाइन 27 (ऑरेंज बॉक्स) पर absTick & 0x1 != 0 असत्य होगा, जो ternary ऑपरेटर के दूसरे भाग को ट्रिगर करेगा।
यदि यह tick 0 है, तो कोई भी अन्य बिट 1 नहीं होगा और इसलिए बाद की कोई भी शर्त if (absTick & 0xXX !=0) सत्य नहीं होगी, जिसका अर्थ है कि ratio बिना किसी अतिरिक्त संशोधन के 0x100000000000000000000000000000000 के बराबर होगा। यह अपेक्षित है, क्योंकि किसी नंबर की घात को शून्य करने पर परिणाम 1 मिलता है।
tick -1 का square root price
यदि हम Python में Q128.128 में tick -1 के price की गणना करते हैं, तो हमें निम्नलिखित मिलता है:
>>>
0xfffcb933bd6f b0000000000000000000 # gap added for clarity
जब इसे hex में बदला जाता है, तो यह नीचे हाइलाइट किए गए magic number के करीब होता है, लेकिन यह स्पष्ट है कि हमारे ऊपर के अनुमान में अंत में अधिक शून्य हैं, जिसका अर्थ है कि इसमें precision loss हुई है:

यहाँ हमारे अनुमान की तुलना में के लिए Uniswap का constant है:
# Uniswap's constant
>>> hex(0xfffcb933bd6fad37aa2d162d1a594001)
0xfffcb933bd6f ad37aa2d162d1a594001 # gap added for clarity
# Our constant
>>> hex(int(2**128 * math.sqrt(1.0001**-1)))
0xfffcb933bd6f b0000000000000000000 # gap added for clarity
# note that Uniswap's and our estimate differ after the gap
अगले भाग में, हम दिखाते हैं कि Uniswap V3 से मेल खाने के लिए अपने अनुमान को कैसे सुधारें।
Decimal का उपयोग करके और divisions को पुनर्व्यवस्थित करके अपनी constant गणनाओं में सुधार करना
डिफ़ॉल्ट रूप से, Python floats में 128 बिट्स की सटीकता वाले नंबरों की गणना करने के लिए पर्याप्त precision नहीं होती है, लेकिन हम Decimal लाइब्रेरी का उपयोग करके इसे ठीक कर सकते हैं जो हमें उतनी precision देती है जितनी हम चाहते हैं:
from decimal import *
getcontext().prec = 100 # use 100 decimals of precision is ~333 bits
इसके अलावा, दशमलव से जुड़ी अशुद्धता को दूर करने के लिए, हम गणना कर सकते हैं:
इस प्रकार:
यह 1.0001 भिन्न को समाप्त कर देता है।
हम नेगेटिव घातांक (जिनमें implied division शामिल है) से बचकर एक बार फिर precision में सुधार कर सकते हैं। ध्यान दें कि हम अंश और हर को पलटकर नेगेटिव घातांक से छुटकारा पा सकते हैं:
Tick -2 की ओर आगे देखते हुए, दूसरे नेगेटिव tick के लिए Decimal(10001)**Decimal(-2/2) (या ) की गणना करने के बजाय, हम इसे सीधे Decimal(10001)**Decimal(-1) तक सरल कर देते हैं ताकि जहाँ division operations की आवश्यकता नहीं है, वहाँ उन्हें कम से कम किया जा सके।
उस बदलाव के साथ, अब हम Uniswap V3 के precomputed मानों को अधिक सटीकता से फिर से उत्पन्न कर सकते हैं। नीचे दिए गए मानों को fixed-point नंबरों में बदलने के लिए उन्हें 2**128 से गुणा किया गया है:
from decimal import *
getcontext().prec = 100
# tick -1
print(hex(int(Decimal(10000)**Decimal(1/2) * 2**128 / Decimal(10001)**Decimal(1/2))))
# estim: 0xfffcb933bd6fad37aa2d162d1a594001
# uniV3: 0xfffcb933bd6fad37aa2d162d1a594001
# tick -2
print(hex(int(Decimal(10000)**Decimal(1) * 2**128 / Decimal(10001)**Decimal(1))))
# estim: 0xfff97272373d413259a46990580e2139
# uniV3: 0xfff97272373d413259a46990580e213a
# tick -4
print(hex(int(Decimal(10000)**Decimal(2) * 2**128 / Decimal(10001)**Decimal(2))))
# estim: 0xfff2e50f5f656932ef12357cf3c7fdcb
# uniV3: 0xfff2e50f5f656932ef12357cf3c7fdcc
# tick -8
print(hex(int(Decimal(10000)**Decimal(4) * 2**128 / Decimal(10001)**Decimal(4))))
# estim: 0xffe5caca7e10e4e61c3624eaa0941ccf
# uniV3: 0xffe5caca7e10e4e61c3624eaa0941cd0
# tick -16
print(hex(int(Decimal(10000)**Decimal(8) * 2**128 / Decimal(10001)**Decimal(8))))
# estim: 0xffcb9843d60f6159c9db58835c926643
# univ3: 0xffcb9843d60f6159c9db58835c926644
ध्यान दें कि उपरोक्त कोड में constants की हमारी गणना वास्तविक कोड मानों से केवल 1 कम है, जिसका अर्थ है कि Uniswap V3 अपने constants प्राप्त करने के लिए दशमलव को round up कर रहा है।
निष्कर्ष के रूप में, getSqrtRatioAtTick() में प्रत्येक “magic numbers” निम्नलिखित नंबर हैं जिन्हें 128-bit fixed point नंबर के रूप में round up करके दर्शाया गया है (tick 1 को छोड़कर)।
Part 4/5: Q128.128 नंबरों के साथ reciprocal की गणना करना
नीचे दिखाई गई कोड की पंक्ति reciprocal की गणना करती है यदि मूल tick पॉजिटिव था।

अब हम समझाते हैं कि कोड अंश में type(uint256).max का उपयोग क्यों करता है। ध्यान दें कि ratio Q128.128 नंबर के रूप में price है।
जब एक fixed-point नंबर को दूसरे fixed-point नंबर से विभाजित किया जाता है, तो हमें scaling factors को आपस में रद्द होने से रोकने के लिए अंश को scaling factor से गुणा करने की आवश्यकता होती है।
Q128.128 में “1” का मान 1 << 128 या है। Q128.128 का उपयोग करके 1 / ratio की गणना करने के लिए हम ऐसा करते हैं:
हमें अंश को scaling factor से गुणा करना होगा, जो है। इससे हमें मिलता है:
हालाँकि, मान को Solidity में एनकोड नहीं किया जा सकता है, सबसे बड़ा मान जिसे एनकोड किया जा सकता है वह या type(uint256).max है।
इसका अर्थ है कि पॉजिटिव ticks के लिए गणना किए गए prices को थोड़ा round down कर दिया जाएगा। इसके प्रभावों पर अंत में चर्चा की गई है।
Part 5/5: ratio को round up करते हुए sqrtPriceX96 में बदलना
कोड की अंतिम पंक्ति ratio को जो कि एक Q128.128 नंबर है, round up करते हुए Q64.96 नंबर में बदल देती है और मान लौटाती है।
sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1));
महत्त्वपूर्ण विवरण जो इस लेख में शामिल नहीं किए गए
हमने इस लेख में कोड के बारे में निम्नलिखित अवलोकन किए हैं:
- Tick -1 को छोड़कर, constants को round up किया गया है।
- पॉजिटिव ticks के लिए reciprocal की गणना करने पर थोड़ा सा round down हो जाता है क्योंकि कोड को
type(uint256).maxके रूप में अनुमानित करता है। - Q128.128 से Q64.96 में अंतिम रूपांतरण round up होता है यदि
ratioको1 << 32से विभाजित करना सटीक नहीं है (ऊपर दिए गए कोड स्निपेट में ternary ऑपरेटर पर ध्यान दें जो 1 जोड़ता है यदिratio,1 << 32को पूरी तरह से विभाजित नहीं करता है)।
सटीकता का अनुभवजन्य परीक्षण (Empirical test of accuracy)
Solidity फ़ंक्शन का परीक्षण केवल कोड को IDE में कॉपी करके किया जा सकता है।
हम निम्नलिखित के लिए सही मान प्राप्त करने के लिए Python में इस प्रकार एक reference implementation बना सकते हैं:
इस प्रकार:
from decimal import *
getcontext().prec = 1000 # set the precision very high
# 2**96 * (1.0001)^(tick/2)
math.ceil(Decimal(2**96)*(Decimal(10001)/Decimal(10000))**(Decimal(tick)/2))
(नोट: आप एक full precision calculator online का भी उपयोग कर सकते हैं)।
हम चरम ticks के लिए आउटपुट की तुलना कर सकते हैं:
# tick 887272 (MAX_TICK)
solidity: 1461446703485210103287273052203988822378723970342
python : 1461446703485210103244672773810124308346321380903
# tick 0
solidity: 79228162514264337593543950336
python : 79228162514264337593543950336
# tick -887272 (MIN_TICK)
solidity: 4295128739
python : 4295128739
यदि हम MAX_TICK की जाँच करते हैं, तो हम देखते हैं कि Solidity कोड वास्तविक मान का अधिक अनुमान लगाता है:
solidity: 14614467034852101032 87273052203988822378723970342
python : 14614467034852101032 44672773810124308346321380903
यह त्रुटि गंभीर है या नहीं, यह इस बात पर निर्भर करता है कि downstream लॉजिक इस फ़ंक्शन का उपयोग कैसे करता है।
getSqrtRatioAtTick() का उपयोग तब किया जाता है जब liquidity providers लिक्विडिटी जोड़ते या हटाते हैं और जब ट्रेडर्स कोई ऐसा स्वैप करते हैं जो एक tick को पार करता है। चूँकि हमने अपने tutorial of Uniswap V3 में इस चरण पर अभी तक इनमें से किसी भी तंत्र पर चर्चा नहीं की है, इसलिए हम इस फ़ंक्शन के error analysis को आगे के लिए टालते हैं।
सारांश
किसी tick पर square root price की गणना करने के लिए, getSqrtRatioAtTick() पहले tick () के absolute value की गणना करता है, और फिर की गणना करने के लिए के 20 most significant bits पर लूप चलाता है। यदि मूल tick पॉजिटिव था, तो यह के रूप में price की फिर से गणना करता है। अंत में, यह 128-bit fixed point representation को 96 bit representation में बदल देता है और उसे price के रूप में लौटाता है।