लेंडर (lender) के डिपॉजिट को ट्रैक करने का सबसे सहज तरीका यह है कि उन्होंने कितनी USDC डिपॉजिट की है और किस समय की है, इसे रिकॉर्ड किया जाए। Compound V3 ऐसा नहीं करता है।
इसके बजाय, SushiSwap Masterchef Staking Algorithm के समान, Compound V3 “शुरुआत से (since the beginning of time)” उधार दिए गए एक डॉलर के काल्पनिक लाभ (hypothetical gain) को ट्रैक करता है। (जो पाठक पहले से इस एल्गोरिथ्म से परिचित नहीं हैं, उन्हें लिंक किए गए संसाधन को पढ़ना चाहिए)।
शुरुआत से उधार दिए गए एक डॉलर के काल्पनिक लाभ को baseSupplyIndex (in CometStorage.sol) में ट्रैक किया जाता है। यह Sushiswap के “rewardPerTokenAccumulator” के समान ही काम करता है। यह 1.0 से शुरू होता है और हर बार जब कोई स्टेट-चेंजिंग ऑपरेशन (state-changing operation) होता है (deposit, withdraw, borrow, आदि), तो इसे बीते हुए समय और उस अवधि की ब्याज दर के अनुपात में बढ़ाया जाता है। उदाहरण के लिए, यदि 100 सेकंड बीत गए, और ब्याज दर 0.001 प्रति सेकंड थी (जो कि अवास्तविक रूप से अधिक है, लेकिन समझने में आसान है), तो baseSupplyIndex को 1.1 पर अपडेट किया जाएगा। विशेष रूप से, निम्नलिखित फॉर्मूले का उपयोग किया जाता है:
baseSupplyIndex += supplyInterestRatePerSecond(utilization) × secondsElapsed
baseSupplyIndex को केवल एक ही जगह बदला जाता है, जो accruedInterestIndices के अंदर 403 in Comet.sol लाइन पर है।

चूंकि ब्याज दरें सीधे utilization का एक फलन (function) होती हैं, इसलिए baseSupplyIndex उच्च utilization के समय तेजी से और कम utilization के समय धीमी गति से बढ़ता है।
निम्नलिखित काल्पनिक प्लॉट दिखाता है कि utilization के आधार पर baseSupplyIndex और baseBorrowIndex अलग-अलग दरों पर बढ़ रहे हैं। आमतौर पर, उधारकर्ता (borrowers) लेंडर्स (lenders) को मिलने वाले ब्याज की तुलना में अधिक ब्याज का भुगतान करते हैं, इसलिए baseBorrowIndex तेजी से बढ़ता है।

निम्नलिखित उदाहरण बताता है कि इस वेरिएबल का उपयोग कैसे किया जाता है।
उदाहरण और शब्दावली (Example and Terminology)
Alice उस समय $1,000 जमा करती है जब baseSupplyIndex 2.5 होता है। उसके अकाउंट में $1,000 जमा करने का क्रेडिट नहीं दिया जाता है, बल्कि $400 जमा करने का क्रेडिट दिया जाता है, जो कि उसके डिपॉजिट को वर्तमान baseSupplyIndex ($1,000 ÷ 2.5) से विभाजित करने पर आता है। Alice के अकाउंट में $400 की “principal value” है (yellow box)। यह वह वैल्यू है जो Compound यूजर्स के लिए CometStorage.sol में स्टोर करता है।

यदि वह तुरंत पैसे निकालती है, तो Compound उसके बैलेंस की गणना $400 को वर्तमान baseSupplyIndex (जो कि 2.5 है) से गुणा करके करेगा, इसलिए वह $1,000 निकालेगी। Compound principal value को baseSupplyIndex से गुणा करने पर मिलने वाली वैल्यू को “present value” कहता है।
Compound V3 यह “याद” नहीं रखता है कि उसका मूल वास्तविक डिपॉजिट $1,000 था। यह निहित (implied) है क्योंकि baseSupplyIndex वर्तमान में 2.5 पर है और उसका डिपॉजिट $400 दर्ज किया गया है।
Compound V3 द्वारा स्टोर की गई “downscaled” या “scaled backward” डॉलर वैल्यू को “principal value” कहा जाता है। जब हम principal value को वर्तमान baseSupplyIndex से गुणा करते हैं, या “scale forward” करते हैं, तो हमें “present value” मिलती है।
पारंपरिक फाइनेंस बैकग्राउंड से आने वाले पाठकों को Compound द्वारा “principal value” और “present value” शब्दों का उपयोग भ्रामक लग सकता है — हम सुझाव देते हैं कि इन शब्दों को उनके पारंपरिक अर्थों से जोड़ने का प्रयास न करें और बस Compound के उपयोग को स्वीकार करें।
यदि वह तब तक प्रतीक्षा करती है जब तक कि baseSupplyIndex बढ़कर 3.0 नहीं हो जाता, तो principal value अभी भी $400 ही रहेगा, लेकिन present value बढ़कर $1,200 ($400 x 3.0 = 1,200) हो जाएगा।

CometCore.sol में, हम देखते हैं कि:
-
“principal value” की गणना “present value” को
baseSupplyIndexसे विभाजित (dividing) करके की जाती है। -
“present value” की गणना “principal value” को
baseSupplyIndexसे गुणा (multiplying) करके की जाती है।

तो महत्वपूर्ण शब्दों को संक्षेप में समझने के लिए:
Principal value
Principal value जमा की गई USDC को जमा करने के समय की baseSupplyIndex वैल्यू से विभाजित करने पर प्राप्त होता है। इसे यूजर के अकाउंट से जुड़े एक स्टोरेज वेरिएबल में रखा जाता है और यह तब तक नहीं बदलता जब तक कि यूजर जमा या निकासी न करे। Principal value आमतौर पर वास्तविक डिपॉजिट से कम होता है क्योंकि baseSupplyIndex हमेशा 1 से अधिक या उसके बराबर होता है।
Present value
Present value, principal value और baseSupplyIndex की वर्तमान वैल्यू (current value) का गुणनफल होता है। यह वैल्यू कहीं भी स्टोर नहीं की जाती है बल्कि आवश्यकतानुसार तुरंत (on the fly) गणना की जाती है।
Compound V3 को समझने के लिए Principal value और present value दो सबसे महत्वपूर्ण कॉन्सेप्ट्स हैं।
लेंडर्स (lenders) के लिए ट्रैक किया जाने वाला एकमात्र वेरिएबल Principal है
आइए ऊपर दिए गए स्ट्रक्ट (struct) के स्क्रीनशॉट पर फिर से नज़र डालते हैं:

baseTrackingAccrued वेरिएबल का उपयोग CometRewards द्वारा यह निर्धारित करने के लिए किया जाता है कि प्रोटोकॉल में भाग लेने के लिए अकाउंट को कितना COMP ईनाम दिया जाना चाहिए। baseTrackingIndex वेरिएबल उसी से संबंधित है लेकिन वर्तमान में अप्रयुक्त (unused) है। assetsIn वेरिएबल का उपयोग केवल उधारकर्ताओं (borrowers) के लिए एक संकेतक के रूप में किया जाता है कि क्या कुछ कोलेटरल एसेट्स (collateral assets) का उपयोग किया गया है। _reserved वेरिएबल अप्रयुक्त है।
इसलिए, लेंडर की अकाउंटिंग के लिए principal ही एकमात्र आवश्यक वेरिएबल है। ध्यान दें कि यह एक साइन्ड (signed) वेरिएबल है — उधारकर्ताओं (borrowers) के लिए यह नेगेटिव होता है। उधारकर्ताओं के लिए, हमें यह भी ट्रैक करने की आवश्यकता होती है कि उन्होंने कितना collateral जमा किया है, लेकिन उस पर चर्चा किसी अन्य लेख में की जाएगी।
balanceOf() — लेंडर का पॉजिटिव बैलेंस चेक करना
यह समझाने के लिए कि Compound V3 principal value को स्टोर करता है, लेकिन अकाउंट को present value पर आँकता है, नीचे दिए गए balanceOf() function in Comet.sol पर विचार करें।
सबसे पहले यह इसे अपडेट किए बिना ही अपडेटेड baseSupplyIndex को पढ़ेगा, क्योंकि यह एक view फंक्शन है। फिर यह लेंडर के principal बैलेंस को पढ़ता है और उसे baseSupplyIndex से गुणा करता है।
उपार्जित ब्याज (Interest accrued) समय और utilization का एक फलन है, इसलिए जब तक utilization शून्य नहीं होता, हर बार जब balanceOf को क्वेरी किया जाता है, यह एक उच्च वैल्यू लौटाएगा।

निम्नलिखित स्क्रीनशॉट Compound V3 dapp पर लेंडर के बैलेंस और उसी एड्रेस के लिए Etherscan से प्राप्त balanceOf रिटर्न वैल्यू के बीच के संबंध को दर्शाता है। दोनों वैल्यूज़ को orange box में हाईलाइट किया गया है।

baseSupplyIndex का विजुअल उदाहरण
जब कोई लेंडर जमा करता है, तो उनके डिपॉजिट के USDC बैलेंस को baseSupplyIndex से विभाजित करके principal value प्राप्त की जाती है, जिसे स्टोर किया जाता है। वे जितना देर से जमा करेंगे, baseSupplyIndex उतना ही बड़ा होगा, और उन्हें कम principal value का क्रेडिट दिया जाएगा।
Principal value स्थिर (static) रहता है (जब तक कि वे जमा या निकासी न करें), लेकिन present value, principal value * baseSupplyIndex होता है, और वह लगातार बढ़ता रहता है।
नीचे दिए गए ग्राफ़िक में, Alice ने $1 जमा किया जब baseSupplyIndex 1.01 के बराबर था, इसलिए उसकी principal value 0.99 (1 ÷ 1.01) है। Bob ने भी $1 जमा किया, लेकिन बाद के समय में जब baseSupplyIndex की वैल्यू 1.03 थी (1 ÷ 0.97)। इसलिए, उसकी principal value 0.97 थी।
Bob और Alice दोनों ने समान राशि जमा की: $1। लेकिन चूंकि Bob ने बाद में जमा किया, इसलिए उसकी principal value कम है।

supplyBase() और withdrawBase()
जब कोई यूजर बेस एसेट (base asset) सप्लाई करता है (या निकालता है), तो principal value रीसेट हो जाती है। उदाहरण के लिए, यदि Alice ने $10 जमा किए जब baseSupplyIndex 10 था, तो उसकी principal value $1 होगी। अब मान लीजिए एक समय पर जब baseSupplyIndex बढ़कर 20 हो जाता है, तो उसके अकाउंट की present value $20 होगी। वह $10 और जोड़ती है। उसकी नई present value $30 होनी चाहिए।
उसकी principal value क्या होनी चाहिए? (एक पल के लिए इस पर विचार करें!)
वर्तमान में baseSupplyIndex 20 है, वर्तमान में present value $30 है, इसलिए उसकी principal value $1.5 ($30 ÷ 20) होनी चाहिए।
इस उदाहरण को ध्यान में रखते हुए, हम supplyBase() function from Comet.sol line 829 दिखाते हैं जिसे तब कॉल किया जाता है जब कोई लेंडर जमा करता है।

जब Alice जमा करती है, तो हम उसकी principal value पढ़ते हैं (orange box), इसे present value में बदलते हैं (green box), और अभी जमा की गई राशि को इसमें जोड़ते हैं (blue box)। यह present value से बदलकर dstPrincipalNew (yellow box) में स्टोर की जाने वाली एक principal value बन जाती है जिसे हम उस यूजर के लिए नई principal value के रूप में स्टोर करते हैं (red box)
ऊपर दिया गया updateBasePrincipal() फंक्शन (red box) यूजर से जुड़े UserBasic स्ट्रक्ट (struct) को अपडेट करता है, और पुराने principal को dstNewPrincipal के साथ ओवरराइट (overwrite) कर देता है।
इसका विपरीत फंक्शन, withdrawBase() (Comet.sol line 1051), उल्टे क्रम में वही लॉजिक लागू करता है।
baseBorrowIndex के बारे में क्या?
जिस तरह से baseSupplyIndex शुरुआत से उधार दिए गए एक डॉलर में हुए लाभ को ट्रैक करता है, उसी तरह baseBorrowIndex शुरुआत से उधार लिए गए एक डॉलर के कर्ज (debt) के संचय को ट्रैक करता है। लेंडर्स और उधारकर्ताओं (borrowers) के लिए अलग-अलग ब्याज दर कर्व (interest rate curves) होते हैं, इसलिए उन्हें ट्रैक करने के लिए दो अलग-अलग वेरिएबल्स की आवश्यकता होती है।
इंडेक्स (indexes) कब ओवरफ्लो (overflow) होंगे?
baseSupplyIndex और baseBorrowIndex दोनों को 15 डेसीमल (decimals) वाले fixed point numbers के रूप में माना जाता है, इसलिए 1e15 को 1.0 माना जाता है। एक साइन्ड 104 बिट (signed 104 bit) नंबर में सबसे बड़ी संख्या 1.014e31 आ सकती है। इसलिए, एक्युमुलेटर (accumulator) में आने वाली सबसे बड़ी संख्या 1.014e16 (15 डेसीमल के साथ) हो सकती है।
मान लेते हैं कि लेंडिंग प्रोटोकॉल कभी भी 100% APR से ऊपर नहीं होगा। इस स्थिति में इंडेक्स को ओवरफ्लो होने में 53 साल लगेंगे। अधिक वास्तविक 10% ब्याज दर का उपयोग करते हुए, एक डॉलर को कंपाउंड होकर 10 ट्रिलियन डॉलर बनने में 386 साल लगेंगे।
यह मानना काफी तर्कसंगत है कि वह दिन आने से पहले Compound वर्जन 4 में अपग्रेड हो जाएगा।
RareSkills के साथ और जानें
अधिक एडवांस्ड Solidity कॉन्सेप्ट्स सीखने के लिए कृपया हमारा Solidity Bootcamp देखें।
मूल रूप से 5 जनवरी, 2024 को प्रकाशित