इस अध्याय में हम Compound V3 के बारे में निम्नलिखित विषयों का परीक्षण करेंगे:
- collateral का मूल्यांकन (valuation)
- अपर्याप्त collateral वाले ऋणों (loans) को एब्जॉर्ब (absorb) करना (liquidations)
- एब्जॉर्ब किए गए collateral को बेचना
- reserves क्या हैं
- और reserves, liquidations को कैसे प्रभावित करते हैं।
एक लेख में चर्चा करने के लिए ये बहुत सारे विषय हैं, लेकिन ये सभी आपस में गहराई से जुड़े हुए हैं, इसलिए बेहतर होगा कि हम इन सभी को एक साथ कवर करें।
पूर्वापेक्षाएँ (Prerequisites)
पाठक को पहले से ही इस बात की जानकारी होनी चाहिए कि Compound V3 principal और present value को कैसे परिभाषित करता है और DeFi liquidations और collateral से परिचित होना चाहिए।
UserBasic Storage Struct
आइए CometStorage.sol में UserBasic struct पर फिर से विचार करें।

यदि principal (नीला बॉक्स) नेगेटिव है, तो इसका अर्थ है कि उपयोगकर्ता एक उधारकर्ता (borrower) है, और यह नेगेटिव वैल्यू उनके ऋण (debt) की principal वैल्यू होगी।
assetsIn (लाल बॉक्स) यह दर्शाने के लिए एक बिटमैप (bitmap) है कि उन्होंने कोई निश्चित collateral एसेट जमा किया है या नहीं। इस लेख को लिखते समय, बिटमैप का लेआउट इस प्रकार है:

baseTrackingIndex और baseTrackingAccrued वेरिएबल्स रिवॉर्ड्स के वितरण का हिसाब-किताब (bookkeeping) रखने के लिए हैं, और इनकी चर्चा एक अलग लेख में की जाएगी। _reserved वेरिएबल अप्रयुक्त (unused) है।
ध्यान दें कि यह struct हमें यह नहीं बताता है कि उपयोगकर्ता के पास कितना collateral है। वह UserCollateral struct में balance वेरिएबल में रखा गया है जो userCollateral नेस्टेड मैपिंग (nested mapping) में स्टोर होता है। _reserved वेरिएबल अप्रयुक्त है।

उपयोगकर्ता द्वारा सप्लाई किए गए collateral की सूची बनाने के लिए, हम Compound द्वारा स्टोर किए गए 0…numAssets के माध्यम से लूप (loop) करते हैं और जांचते हैं कि क्या वह बिट (bit) उस उपयोगकर्ता के लिए एक (1) पर सेट है। यदि ऐसा है, तो हम उस बिट से जुड़े टोकन एड्रेस को प्राप्त करते हैं और userCollateral[user][collateralAsset] में उपयोगकर्ता का बैलेंस जांचते हैं ताकि यह देखा जा सके कि उपयोगकर्ता के पास उस collateral की कितनी मात्रा है।
balance को ओरेकल (oracle) प्राइस से गुणा करके, हम उपयोगकर्ता के collateral की डॉलर वैल्यू जान सकते हैं। नीचे दी गई तालिका उपयोगकर्ता के collateral की कुल वैल्यू को जोड़ने का एक उदाहरण देती है।

AssetInfo
ओरेकल का वह एड्रेस जहां से Compound collateral की कीमत प्राप्त करता है, AssetInfo struct (नीला बॉक्स) में स्टोर किया जाता है।

ध्यान दें कि ऊपर दिया गया AssetInfo struct 432 बिट्स (bits) बड़ा है — इसे स्टोर करने में 2 स्लॉट (slots) लगते हैं। हम अगले अनुभाग में इस पर फिर से विचार करेंगे।
Compound Finance Markets पर AssetInfo प्रदर्शित करना
आइए ऊपर दिखाए गए AssetInfo struct की सामग्री की तुलना Compound Finance Market UI से करें। यहां हम प्रदर्शित की गई अधिकांश जानकारी देखते हैं।
दस्तावेज़ और कोड हमें यह नहीं बताते कि scale वेरिएबल का उपयोग किस लिए किया जाता है, लेकिन इसमें 1e18 नंबर होता है, इसलिए संभवतः यह उपभोक्ता को यह बताने के लिए है कि प्रतिशत (percentages) को कैसे स्केल (scale) किया जाए।
इन वेरिएबल्स का अर्थ liquidations और collateral पर लिखे गए लेख में समझाया गया था।

जब हम टोकन UNI (assetId 3) के लिए वर्तमान मानों को क्वेरी (query) करते हैं (https://etherscan.io/address/0xc3d688B66703497DAA19211EEdff47f25384cdc3#readProxyContract#F16), तो हम उन मानों की तुलना मार्केटप्लेस पर प्रदर्शित मानों से कर सकते हैं। इनके बीच का संबंध स्पष्ट होना चाहिए। ध्यान देने योग्य बात: लिक्विडेशन पेनल्टी (liquidation penalty) 1 - liquidation factor है। liquidateCollateralFactor वह LTV है जिस पर ऋण लिक्विडेट होता है। liquidationFactor लिक्विडेशन पेनल्टी को एन्कोड करता है। यह तथ्य कि struct में liquidationFactor का अर्थ UI में liquidationFactor के समान नहीं है, भ्रमित करने वाला (confusing) है।
ऊपर की छवि स्क्रीनशॉट है, और नीचे दिए गए मान टोकन UNI के लिए getAssetInfo() को क्वेरी करने वाले Etherscan का स्क्रीनशॉट हैं।
नीचे हम Compound UI पर UNI collateral मापदंडों (parameters) और टोकन UNI के लिए getAssetInfo() को क्वेरी करने वाले Etherscan के बीच संबंध दिखाते हैं।

अगला स्वाभाविक प्रश्न यह है कि, “Compound AssetInfo structs को कहाँ स्टोर करता है?”
AssetInfo “structs” को immutable वेरिएबल्स में रखा जाता है
प्रत्येक एसेट की जानकारी को immutable वेरिएबल्स में पैक (packed) किया जाता है — गैस दक्षता (gas efficiency) के उद्देश्यों के लिए इसे स्टोरेज में नहीं रखा जाता है। चूंकि AssetInfo struct को स्टोर करने में दो 32 बाइट वर्ड्स (words) लगते हैं, इसलिए Comet assetXX_a, assetXX_b के साथ uint256 वर्ड्स की नंबरिंग करता है। यहां XX एसेट इंडेक्स (asset index) को दर्शाता है। इसलिए asset00_a और asset00_b सामूहिक रूप से एसेट 0 के लिए AssetInfo struct को होल्ड करते हैं। याद रखें, AssetInfo को स्टोर करने में दो 256 बिट वेरिएबल्स लगते हैं, जो कि 432 बिट्स बड़ा है।

अब हम Comet.sol:280-356 से getAssetInfo() का इम्प्लीमेंटेशन दिखा सकते हैं। यह बस immutable वेरिएबल को AccountInfo struct में अनपैक (unpack) करता है और इसे रिटर्न (return) करता है। इसके द्वारा उपयोग की जाने वाली बिटशिफ्टिंग (bitshifting) और पैकिंग काफी सीधी है, इसलिए हम इसे यहां स्पष्ट नहीं करेंगे।

चूंकि ये वेरिएबल्स immutable हैं, इसलिए यदि गवर्नेंस (governance) कोई अन्य collateral एसेट जोड़ना चाहता है या किसी एसेट के मापदंडों को बदलना चाहता है, तो उसे एक नया इम्प्लीमेंटेशन डिप्लॉय (deploy) करना होगा और प्रॉक्सी (proxy) को अपडेट करना होगा। इस बात का ध्यान रखा जाना चाहिए कि केवल एसेट्स को जोड़ा (append) जाए और पिछली परिभाषाओं (definitions) के साथ हस्तक्षेप न किया जाए।
यह जाँचना कि क्या किसी उधारकर्ता को लिक्विडेट किया जा सकता है
Comet.sol का isLiquidatable() function उपयोगकर्ता के पास मौजूद collateral एसेट्स को उनके liquidationFactor से गुणा करके जोड़ता है। यदि यह योग (sum) उनके ऋण (debt) की वर्तमान वैल्यू (जो एक नेगेटिव संख्या है) से कम है, तो उपयोगकर्ता लिक्विडेट किए जाने योग्य (liquidatable) है।

इसका अर्थ यह है कि किसी उधारकर्ता के पास लिक्विडेशन थ्रेशोल्ड (liquidation threshold) से नीचे एक एसेट हो सकता है, लेकिन यदि अन्य collateral एसेट्स उस कमी को संतुलित (balance out) कर देते हैं, तो उपयोगकर्ता लिक्विडेट नहीं किया जा सकता।
collateral की पूरी वैल्यू उपयोगकर्ता के collateral बैलेंस में “गिनी (count)” नहीं जाती है — इसे लिक्विडेशन फैक्टर द्वारा कम कर दिया जाता है।
यहां पहले वाला ही उदाहरण दिया गया है जो उपयोगकर्ता के collateral एसेट्स की वास्तविक वैल्यू को दर्शाता है।

ऊपर दिए गए उदाहरण में, यदि काल्पनिक उधारकर्ता का ऋण बैलेंस (loan balance) $8,360 से अधिक हो जाता है, तो उसे लिक्विडेट कर दिया जाएगा।
किसी उधारकर्ता को लिक्विडेट करना (absorb)
यदि isLiquidatable() फ़ंक्शन true रिटर्न करता है, तो उधारकर्ता के collateral को प्रोटोकॉल में एब्जॉर्ब (absorb) किया जा सकता है। जिसे कुछ प्रोटोकॉल “लिक्विडेशन” कहते हैं, Compound V3 उसे “एब्जॉर्ब (absorb)” कहता है।
एब्जॉर्ब (Absorb) पूरी तरह से होता है या बिल्कुल नहीं (all or nothing) — Compound V3 में collateral के एक हिस्से को लिक्विडेट करने का कोई विकल्प नहीं है। उधारकर्ता के पूरे एसेट बैलेंस को शून्य (zero) पर सेट कर दिया जाता है।
Absorb का उदाहरण
मान लीजिए बॉब ने Compound V3 में $1000 का ETH जमा किया और $800 USDC उधार लिए। यह 80% के कोलैटरलाइजेशन रेश्यो (collateralization ratio) को संतुष्ट करता है। ETH की वैल्यू गिरकर $880 हो जाती है जिससे LTV (loan to value) 90.9% तक पहुंच जाता है, जो 90% लिक्विडेशन थ्रेशोल्ड को ट्रिगर कर देता है।
एक लिक्विडेटर बॉब के खाते पर absorb() को कॉल करता है और $880 का ETH collateral प्रोटोकॉल में एब्जॉर्ब हो जाता है।
मान लेते हैं कि लिक्विडेशन पेनल्टी 5% है।
चूंकि वर्तमान में collateral की वैल्यू $880 ETH है, इसलिए इसका 5% $44 ETH में होता है।
प्रोटोकॉल पेनल्टी के रूप में बॉब के collateral से $44 की कटौती करेगा, जिससे $836 बचेंगे। चूंकि बॉब ने $800 USDC उधार लिए थे, इसलिए $36 का सरप्लस (surplus) है। यानी, ऋण चुकाने के लिए प्रोटोकॉल द्वारा $800 ले लिए जाते हैं और $36 बच जाते हैं। इसे बॉब के खाते में क्रेडिट कर दिया जाता है जो अब $36 USDC जमा के साथ एक ऋणदाता (lender) बन जाता है।
बॉब ने ऋण लेते समय पहले ही $800 USDC निकाल लिए हैं, इसलिए उसकी कुल होल्डिंग्स (holdings) अब $836 हैं।
ध्यान दें कि absorb() इंटरैक्शन में किसी भी चीज़ ने सीधे तौर पर लिक्विडेटर को पुरस्कृत (reward) नहीं किया।
ध्यान दें कि जब एक उधारकर्ता लिक्विडेट हो जाता है, तो वह ऋणदाता बन जाएगा यदि collateral ऋण को कवर करने के लिए पर्याप्त है।
यदि collateral ऋण को कवर करने के लिए पर्याप्त नहीं है, तो प्रोटोकॉल परोक्ष रूप से अपने reserves से नुकसान उठाता है जिसकी चर्चा हम आगे करेंगे।
Reserves
उधारकर्ताओं द्वारा भुगतान किया गया वह ब्याज (interest) जो ऋणदाताओं द्वारा अर्जित किए गए ब्याज से अधिक होता है, Compound V3 में “reserves” कहलाता है।
Reserves का उदाहरण
ऐलिस प्रोटोकॉल को 100 USDC उधार देती है और 5% ब्याज अर्जित करती है। बॉब प्रोटोकॉल से 100 USDC उधार लेता है और 10% ब्याज देता है। सरलता के लिए, मान लेते हैं कि ऐलिस और बॉब सिस्टम में एकमात्र उपयोगकर्ता हैं। बॉब द्वारा भुगतान किया गया 5% अतिरिक्त ब्याज होगा जो ऐलिस ने अर्जित नहीं किया। यह अतिरिक्त राशि ही रिज़र्व (reserve) है।
इससे कोई फर्क नहीं पड़ता कि बॉब ने ऋण चुकाया है या नहीं (यानी क्या उसने प्रोटोकॉल को अभी तक 110 USDC ट्रांसफर किए हैं या नहीं)। उस पर प्रोटोकॉल के 110 USDC बकाया हैं और प्रोटोकॉल पर ऐलिस के 105 USDC बकाया हैं। इसलिए, reserves में 5 USDC हैं।
मान लीजिए बॉब ऋण चुका देता है। अब प्रोटोकॉल का बैलेंस 110 USDC है, जिसमें से 105 ऐलिस को देय (due) हैं। reserves में अभी भी 5 USDC हैं — कुछ भी नहीं बदला।
getReserves() फ़ंक्शन यह वैल्यू रिटर्न करता है। प्रोटोकॉल के “स्वामित्व (owns)” वाला USDC निम्नलिखित का योग है:
-
Compound के पास मौजूद USDC का बैलेंस यानी
ERC20(baseToken).balanceOf(address(this))और -
totalBorrowकी वर्तमान वैल्यू (present value), -
माइनस (-) वह राशि जो प्रोटोकॉल को ऋणदाताओं को चुकानी है, यानी
totalSupplyकी वर्तमान वैल्यू।
दूसरे शब्दों में, यह usdc_balance + totalBorrow - totalSupply है।
जैसा कि आप नीचे दिए गए फ़ंक्शन में देख सकते हैं, totalSupply को एक नेगेटिव चिह्न (negative sign) दिया गया है क्योंकि यह वह राशि है जो Compound को ऋणदाताओं को चुकानी है। पॉजिटिव कारक (positive factors) — रखे गए USDC की राशि और उधारकर्ताओं से Compound को देय USDC की शुद्ध राशि — Compound के “स्वामित्व” वाली USDC की राशि हैं।

यदि हम Etherscan पर getReserves() function को देखते हैं, तो हम देखेंगे कि लिखते समय रिज़र्व्स 3.47 मिलियन USD (6 दशमलव) हैं।

जब हम USDC / Mainnet Compound market को देखते हैं, तो हम यह भी देखते हैं कि फ्रंट-एंड वर्तमान रिज़र्व्स को 3.47 मिलियन के रूप में प्रदर्शित कर रहा है।

यदि हम absorb() से पहले और बाद में getReserves() को कॉल करते हैं, तो हम देखेंगे कि रिज़र्व्स में कमी आई है। ऐसा दो कारणों से होता है:
-
प्रोटोकॉल ने ऋण चुका दिया (इसलिए उसे कम बकाया मिलना है)। यह रिज़र्व्स से निकाला गया, इसलिए स्वाभाविक रूप से रिज़र्व्स कम हो गए हैं।
-
उधारकर्ता एक छोटी जमा राशि (deposit) के साथ ऋणदाता बन जाता है। यह जमा राशि Compound द्वारा ऋणदाता को देय है, जिससे रिज़र्व्स और कम हो जाते हैं।
withdrawReserves()
अतिरिक्त (excess) रिज़र्व गवर्नेंस (governance) द्वारा उपयोग के लिए है और इसे नीचे दिए गए फ़ंक्शन का उपयोग करके निकाला (withdraw) जा सकता है।

Target Reserves
Compound में एक पब्लिक immutable वेरिएबल है Comet.sol में परिभाषित targetReserves

जब हम Etherscan पर targetReserves देखते हैं, तो हम पाते हैं कि यह 5 मिलियन USDC है।

प्रोटोकॉल में Target reserves का केवल एक ही उपयोग है: यह निर्धारित करना कि क्या प्रोटोकॉल के पास पर्याप्त “मार्जिन ऑफ सेफ्टी (margin of safety)” है ताकि वह एब्जॉर्ब किए गए collateral को न बेचे। ध्यान दें कि गवर्नेंस (governance) एक नया Comet इंस्टेंस (instance) डिप्लॉय करके इस वैल्यू को बदल सकता है।
यानी, यदि Compound V3 के पास पर्याप्त “अतिरिक्त नकदी (excess cash)” है, तो वे इस अनुमान (speculating) के उद्देश्य से collateral को अपने पास रखना पसंद करेंगे कि इसकी वैल्यू में वृद्धि होगी।
आइए उस एकमात्र फ़ंक्शन की जांच करें जहां इस वेरिएबल का उपयोग किया जाता है।
buyCollateral()
एक absorb के बाद भी Collateral प्रोटोकॉल के अंदर ही रहता है। बस इतना ही हुआ कि उपयोगकर्ता का collateral बैलेंस शून्य पर सेट कर दिया गया — हालाँकि collateral को कहीं भी ट्रांसफर नहीं किया गया था। यह collateral अभी भी Compound V3 के “अंदर” है।
लिक्विडेटर्स को प्रोत्साहित करने के लिए, Compound द्वारा रखे गए collateral को buyCollateral() function के माध्यम से छूट (discount) पर बेचा जाता है।
इस फ़ंक्शन में बिज़नेस लॉजिक (business logic) के दो महत्वपूर्ण हिस्से हैं:
-
यदि reserves की राशि target reserves ($5 मिलियन) से अधिक है, तो यह फ़ंक्शन रिवर्ट (revert) हो जाएगा, जिससे लिक्विडेटर्स को collateral खरीदने की अनुमति नहीं मिलेगी। (नीचे दिए गए कोड में पीला बॉक्स)। जैसा कि ऊपर बताया गया है, Compound collateral पर सट्टा (speculate) लगाना चाहता है। चूंकि यह पहले से ही नकदी-भारी (cash-heavy) स्थिति में है, इसलिए वे अधिक नकदी जमा नहीं करना चाहते हैं।
-
जिस विनिमय दर (exchange rate) पर प्रोटोकॉल collateral बेचता है, वह
quoteCollateral()फ़ंक्शन द्वारा निर्धारित की जाती है (नीचे दिए गए कोड में लाल बॉक्स)।
बाकी का कोड स्वतः स्पष्ट (self-explanatory) है।

Liquidation bot
किसी उधारकर्ता को लिक्विडेट करने के लिए, लिक्विडेटर उधारकर्ता के खाते को आर्ग्युमेंट्स (arguments) के रूप में देकर absorb() को कॉल करता है, फिर उसी ट्रांज़ैक्शन में buyCollateral() को कॉल करता है। लिक्विडेटर को यह जांचना चाहिए कि reserves, target reserves से अधिक न हों, और isLiquidateable() के माध्यम से खाता लिक्विडेट करने योग्य (liquidatable) हो। Compound V3 एक रेफरेंस लिक्विडेटर बॉट (reference liquidator bot) प्रदान करता है। ध्यान रखें कि यह एक रेफरेंस इम्प्लीमेंटेशन है — दूसरों से पहले लिक्विडेशन प्राप्त करने का प्रयास करना अत्यधिक प्रतिस्पर्धी (competitive) है, इसलिए दूसरों से पहले किसी पोज़िशन को लाभप्रद रूप से लिक्विडेट करने में सक्षम होने के लिए आपके कोड को अत्यधिक गैस ऑप्टिमाइज़्ड (gas optimized) होना आवश्यक है।
सारांश (Summary)
Compound V3 जिन एसेट्स को collateral के रूप में स्वीकार करता है उनकी सूची — और साथ ही उनके मापदंड जैसे कोलैटरलाइजेशन रेश्यो, लिक्विडेशन रेश्यो, ओरेकल एड्रेस, इत्यादि, immutable वेरिएबल्स में “पैक” किए गए हैं। इन्हें बदलने के लिए, Compound V3 में एक प्रॉक्सी अपग्रेड (proxy upgrade) आवश्यक है।
किसी उपयोगकर्ता का collateral बैलेंस एक बिटमैप (bitmap) के संयोजन के माध्यम से ट्रैक किया जाता है जो यह दर्शाता है कि क्या उनके पास किसी निश्चित एसेट के लिए शून्य-रहित (non-zero) बैलेंस है, और फिर borrower ⇒ asset ⇒ assetBalance की एक नेस्टेड मैपिंग (nested mapping) के माध्यम से ट्रैक किया जाता है। उनका कुल collateral बैलेंस प्रत्येक एसेट और ओरेकल कीमत के गुणनफल (multiplied) का योग है।
Liquidations पूरी तरह से होते हैं या बिल्कुल नहीं (all-or-nothing)। जब किसी उपयोगकर्ता को लिक्विडेट किया जाता है, तो वे अपने collateral का 1 - liquidationRatio खो देंगे और बचे हुए हिस्से का उपयोग ऋण चुकाने और उपयोगकर्ता को एक पॉजिटिव बैलेंस असाइन (assign) करने के लिए किया जाएगा।
अब प्रोटोकॉल के पास अतिरिक्त (excess) collateral है और वह इसे quoteCollateral() फ़ंक्शन के अनुसार छूट पर बिक्री के लिए उपलब्ध कराता है। हालाँकि, यदि reserves targetReserves से अधिक हैं, तो यह इसे नहीं बेचेगा।
Reserves मुख्य रूप से वह पैसा है जो प्रोटोकॉल पर बकाया है, प्लस प्रोटोकॉल का USDC बैलेंस, माइनस वह राशि जो प्रोटोकॉल को ऋणदाताओं को चुकानी है। यह पैसा गवर्नेंस द्वारा निकाला (withdrawable) जा सकता है।
RareSkills के साथ और जानें
उन्नत तकनीकी web3 पाठ्यक्रमों के लिए हमारे ब्लॉकचेन बूटकैंप (blockchain bootcamp) को देखें।
मूल रूप से 8 जनवरी, 2024 को प्रकाशित