यह लेख बीजगणितीय (algebraically) रूप से चरण-दर-चरण दिखाता है कि get_D() और get_y() का कोड StableSwap invariant से कैसे प्राप्त किया जाता है।
दिए गए StableSwap Invariant के अनुसार:
Ann∑xi+D=AnnD+nn∏xiDn+1
इसके साथ हम दो सामान्य गणितीय ऑपरेशन (math operations) करना चाहते हैं:
A और reserves x1,…,xn के निश्चित मान (fixed values) दिए होने पर D की गणना करना। ध्यान दें कि n, यानी उन कॉइन्स की संख्या जिन्हें पूल सपोर्ट करता है, पूल के डिप्लॉय होने के समय ही निश्चित (fixed) हो जाती है। function get_D() यही काम करता है।
D दिए होने पर, हम किसी एक reserve xi का मान बढ़ाकर एक नए मान xi′ तक ले जाना चाहते हैं और यह पता लगाना चाहते हैं कि समीकरण (equation) को संतुलित (balanced) रखने के लिए किसी अन्य reserve xj को कितना कम करने की आवश्यकता है। function get_y() यही काम करता है। यहाँ “y” का अर्थ xi′ है।
Curve StableSwap में इन ऑपरेशन्स को क्रमशः get_D() और get_y() कहा जाता है।
get_D() का उद्देश्य
Curve V1 (StableSwap) में, D बिल्कुल Uniswap V2 के k के समान व्यवहार करता है — D जितना बड़ा होगा, reserves उतने ही अधिक होंगे, और मूल्य वक्र (price curve) उतना ही “आगे की ओर (further out)” होगा। liquidity जोड़ने या हटाने के बाद, या जब कोई फीस पूल बैलेंस को बदल देती है, तो D बदल जाता है — और इसकी दोबारा गणना (recompute) करने की आवश्यकता होती है। function get_D() का उद्देश्य यही है। पूल के वर्तमान reserves के आधार पर, यह D की गणना करता है।
यदि एक curve पूल में दो टोकन, x और y हैं, तो StableSwap invariant है
4A(x+y)+D=4AD+4xyD3
हमारे उद्देश्यों के लिए, “amplification factor” A को एक स्थिरांक (constant) के रूप में माना जा सकता है।
get_y() का उद्देश्य
function get_y() का उपयोग स्वैप (swap) के दौरान किया जाता है। Uniswap V2 में k के समान, स्वैप के दौरान (फीस को छोड़कर) D को स्थिर (constant) रखा जाना चाहिए। विशेष रूप से, x का नया मान दिए जाने पर, यह y के उस मान की गणना करता है जो समीकरण को संतुलित रखता है। इस प्रकार, यह पता लगाने के लिए एक महत्वपूर्ण सबरूटीन (subroutine) है कि “यदि मैं पूल में इतना टोकन x डालता हूँ, तो कितना टोकन y निकाला जा सकता है?”
Curve पूल में 2 से अधिक टोकन रख सकता है (उदा. 3pool में USDT, USDC और DAI होते हैं)। Curve एक array में index द्वारा कॉइन्स की पहचान करता है। तो, इस मामले में, x और y उस array में विशिष्ट कॉइन्स को संदर्भित करते हैं। इस संदर्भ में, get_y() का अर्थ है किसी विशिष्ट टोकन x का बैलेंस बदलना, जबकि अन्य बैलेंस को स्थिर रखना, लेकिन किसी अन्य टोकन y के मान को बदलने की अनुमति देना। फिर, x में एक विशेष बदलाव दिए जाने पर, यह गणना करना कि invariant को संतुलित रखने के लिए y कैसे बदलता है।
n टोकन्स के लिए invariant है:
Ann∑xi+D=AnnD+nn∏xiDn+1
सरलता के लिए, हम बाकी लेख में summation के बजाय S और product के बजाय P का उपयोग करेंगे, इसलिए invariant बन जाता है:
AnnS+D=ADnn+nnPDn+1
जहाँ S टोकन्स के बैलेंस का योग (sum) है (x0+x1+…+xn), P बैलेंस का गुणनफल (product) है (x0x1...xn), और xi टोकन i का बैलेंस है।
whitepaper में, S को ∑xi के रूप में और P को ∏xi के रूप में लिखा गया है। whitepaper का समीकरण (equation) नीचे दोहराया गया है:
Ann∑xi+D=ADnn+nn∏xiDn+1
हम sum और product नोटेशन के बजाय S और P का उपयोग करेंगे।
हम मान लेते हैं कि पूल में मनमाने ढंग से कितने भी n टोकन हो सकते हैं, इसलिए सूत्र (formulas) उसी को दर्शाएंगे। हालांकि, व्यवहार में, n छोटा होना चाहिए, अन्यथा Dn+1 पद (term) के overflow होने की संभावना रहती है।
get_D() के साथ D की गणना करना
get_D() में, हमें बैलेंस का एक सेट x_0, x_1, ..., x_n दिया जाता है और हमें D की गणना करनी होती है।
बीजगणितीय (algebraically) रूप से इसे हल करना संभव नहीं है
AnnS+D=ADnn+nnPDn+1
D के लिए। इसके बजाय, हमें इसे संख्यात्मक रूप से (numerically) हल करने के लिए Newton’s method लागू करने की आवश्यकता है। ऐसा करने के लिए, हम एक फ़ंक्शन f(D) बनाते हैं, जो समीकरण के संतुलित होने पर 0 होता है।
0=ADnn+nnPDn+1−D−AnnS
0=nnPDn+1+ADnn−D−AnnS
f(D)=nnPDn+1+AnnD−D−AnnS
और हम D के संदर्भ में व्युत्पन्न (derivative) f′(D) की गणना इस प्रकार करते हैं:
f′(D)=nnP(n+1)Dn+Ann−1
Newton’s Method का फॉर्मूला
हम पुनरावृत्ति (iteratively) द्वारा D को इसका उपयोग करके हल कर सकते हैं:
Dnext=D−f′(D)f(D)
f′(D) को हर (denominator) में D के साथ व्यक्त करना उपयोगी होगा। सबसे पहले हम f′(D) को परिभाषित करने वाले बाएँ भिन्न (fraction) के अंश (top) और हर (bottom) को D से गुणा करते हैं।
f′(D)=DnnP(n+1)Dn+1+Ann−1
और फिर f′(D) को एक एकल भिन्न (single fraction) में मिलाएँ:
D_P: uint256 = D # D_P = Sfor _x in xp: D_P = D_P * D / (_x * N_COINS)
xp टोकन्स की संख्या है, इसलिए लूप n बार चलेगा। इसलिए, हमारे पास हर (denominator) में D को स्वयं से n बार गुणा किया गया है
Dp=nn∏i=1nxiDn+1
get_y() के साथ y की गणना करना
विचार यह है कि हम xi में से किसी एक को एक नया मान लेने के लिए बाध्य करते हैं (कोड इसे x कहता है) और किसी अन्य xj (जहाँ i=j) के लिए सही मान की गणना करते हैं ताकि समीकरण संतुलित रहे। अन्य टोकन्स का बैलेंस अपरिवर्तित (unchanged) रहता है। xj को y के रूप में संदर्भित किया जाता है।
हालाँकि एक StableSwap पूल में कई टोकन हो सकते हैं, लेकिन get_y() का उपयोग करके एक समय में उनमें से केवल दो टोकन का आदान-प्रदान (exchange) करना ही संभव है।
फिर से, हमारे पास वही invariant है
AnnS+D=ADnn+nnPDn+1
D, A, और n निश्चित (fixed) हैं, लेकिन हम S और P में दो मानों को बदलेंगे
SP=x0+x1+...+xn=x0x1...xn
इसलिए, हमें सूत्र (formula) को थोड़ा समायोजित (adjust) करने की आवश्यकता है, क्योंकि S और P में वे मान शामिल हैं जिनकी हम गणना कर रहे हैं।
S′ उन सभी बैलेंस का योग (sum) होगा, सिवाय टोकन xi के उस नए बैलेंस के, जिसे हम हल करने का प्रयास कर रहे हैं
P उन सभी टोकन के बैलेंस का गुणनफल (product) होगा, सिवाय उसके जिसे हम हल करने का प्रयास कर रहे हैं।
दूसरे शब्दों में,
SP=S′+y=P′y
कोड के साथ एकरूपता (consistency) बनाए रखने के लिए, हम उस टोकन को y कहेंगे जिसके नए बैलेंस की हम गणना करने का प्रयास कर रहे हैं।
तब सूत्र यह बन जाता है:
Ann(S′+y)+D=ADnn+nnP′yDn+1
फिर से, हम एक f(y) प्राप्त करते हैं जो समीकरण के संतुलित होने पर 0 होता है, और y के संदर्भ में इसका व्युत्पन्न (derivative) निकालते हैं:
अपने invariant पर वापस जाते हुए, हम हर (denominator) में भिन्नात्मक पद (fractional term) के लिए हल कर सकते हैं:
Ann(S′+y)+D=ADnn+nnP′yDn+1
nnP′yDn+1=Ann(S′+y)+D−ADnn
फिर हम उसे ynext के समीकरण में प्रतिस्थापित (substitute) कर सकते हैं:
ynext=nnP′yDn+1Ann1+yy2+nnP′AnnDn+1
ynext=(Ann(S′+y)+D−ADnn)Ann1+yy2+nnP′AnnDn+1
फिर हम Ann1 को वितरित (distribute) कर सकते हैं और हर (denominator) को सरल कर सकते हैं
ynext=((S′+y)+AnnD−D)+yy2+nnP′AnnDn+1
ynext=(S′+y+AnnD−D)+yy2+nnP′AnnDn+1
कोष्ठक (parenthesis) हटाकर और दोनों y को एक साथ जोड़कर हर (denominator) को सरल करें
ynext=2y+S′+AnnD−Dy2+nnP′AnnDn+1
मूल कोड में, Curve अतिरिक्त वेरिएबल परिभाषित करता है:
c=nnP′AnnDn+1
b=S′+AnnD
ynext के सूत्र में प्रतिस्थापन (substitution) के बाद, हमें मिलता है:
ynext=2y+S′+AnnD−Dy2+nnP′AnnDn+1
ynext=2y+b−Dy2+c
मूल सोर्स कोड (original source code) से तुलना
यह बिल्कुल Curve कोड से मेल खाता है, नीचे दिए गए बैंगनी बॉक्स को देखें:
Ann और An**ⁿ के बीच बेमेल (Mismatch)
थोड़ा भ्रामक (confusing) रूप से, Curve whitepaper invariant Ann का उपयोग करता है लेकिन कोडबेस Ann का उपयोग करता है। अर्थात्, कोडबेस A * n ** n के बजाय A * n * n की गणना करता हुआ प्रतीत होता है। इस विसंगति (discrepancy) का कारण यह है कि कोडबेस A को Ann−1 के रूप में स्टोर करता है। चूँकि डिप्लॉयमेंट के समय n निश्चित (fixed) होता है, nn−1 की पहले से गणना (precomputing) करने से कोड को ऑन-चेन घातांक (exponent) की गणना करने से बचने में मदद मिलती है, जो एक अधिक महँगा ऑपरेशन (costly operation) है।
सारांश (Summary)
Curve का मूल invariant वेरिएबल्स D या xi को प्रतीकात्मक रूप से (symbolically) हल करने की अनुमति नहीं देता है। इसके बजाय, पदों (terms) को संख्यात्मक रूप से (numerically) हल किया जाना चाहिए।
इस अभ्यास से एक निष्कर्ष (takeaway) यह निकलता है कि अच्छा बीजगणितीय हेरफेर (algebraic manipulation) एक बहुत प्रभावी gas optimization तकनीक है। Curve डेवलपर्स Newton’s method का एक ऐसा फॉर्मूला निकालने में सक्षम थे जो f और इसके व्युत्पन्न (derivative) को सीधे तौर पर प्लग इन करने और उसे वैसे ही छोड़ देने की तुलना में बहुत छोटा है।
संदर्भ और आभार (Citations and Acknowledgements)
इस लेख को लिखने में निम्नलिखित संसाधनों (resources) से परामर्श लिया गया था: