मुख्य आशयावर जा

युनिस्वॅप-v2 कॉन्ट्रॅक्ट वॉक-थ्रू

solidity
डॅप्स
मध्यम
ओरी पोमेरँट्झ
1 मे, 2021
56 मिनिटांचे वाचन
पृष्ठ संपादित करा (opens in a new tab)

परिचय

युनिस्वॅप v2 (opens in a new tab) कोणत्याही दोन ERC-20 टोकन्स दरम्यान विनिमय बाजार (exchange market) तयार करू शकते. या लेखामध्ये आपण हा प्रोटोकॉल लागू करणाऱ्या कॉन्ट्रॅक्ट्सच्या सोर्स कोडवर नजर टाकू आणि ते अशा प्रकारे का लिहिले आहेत ते पाहू.

युनिस्वॅप काय करते?

मूलतः, वापरकर्त्यांचे दोन प्रकार आहेत: तरलता प्रदाते आणि व्यापारी.

तरलता प्रदाते पूलला दोन टोकन्स प्रदान करतात ज्यांची अदलाबदल केली जाऊ शकते (आपण त्यांना Token0 आणि Token1 म्हणू). त्या बदल्यात, त्यांना एक तिसरे टोकन मिळते जे पूलच्या आंशिक मालकीचे प्रतिनिधित्व करते, ज्याला तरलता टोकन म्हटले जाते.

व्यापारी पूलमध्ये एका प्रकारचे टोकन पाठवतात आणि तरलता प्रदात्यांनी प्रदान केलेल्या पूलमधून दुसरे टोकन प्राप्त करतात (उदाहरणार्थ, Token0 पाठवा आणि Token1 प्राप्त करा). विनिमय दर पूलमध्ये असलेल्या Token0 आणि Token1 च्या सापेक्ष संख्येनुसार निर्धारित केला जातो. याव्यतिरिक्त, पूल तरलता पूलसाठी बक्षीस म्हणून एक छोटी टक्केवारी घेतो.

जेव्हा तरलता प्रदात्यांना त्यांची मालमत्ता परत हवी असते, तेव्हा ते पूल टोकन्स जाळू शकतात आणि बक्षिसांमधील त्यांच्या वाट्यासह त्यांचे टोकन्स परत मिळवू शकतात.

अधिक सविस्तर वर्णनासाठी येथे क्लिक करा (opens in a new tab).

v2 का? v3 का नाही?

युनिस्वॅप v3 (opens in a new tab) हे एक अपग्रेड आहे जे v2 पेक्षा खूपच गुंतागुंतीचे आहे. आधी v2 शिकणे आणि नंतर v3 कडे जाणे सोपे आहे.

कोर कॉन्ट्रॅक्ट्स विरुद्ध पेरिफेरी कॉन्ट्रॅक्ट्स

युनिस्वॅप v2 दोन घटकांमध्ये विभागलेले आहे, एक कोर (core) आणि एक पेरिफेरी (periphery). या विभागणीमुळे कोर कॉन्ट्रॅक्ट्स, ज्यांच्याकडे मालमत्ता असते आणि त्यामुळे ते सुरक्षित असणे आवश्यक असते, अधिक सोपे आणि ऑडिट करण्यासाठी सोपे होतात. व्यापाऱ्यांना आवश्यक असलेली सर्व अतिरिक्त कार्यक्षमता नंतर पेरिफेरी कॉन्ट्रॅक्ट्सद्वारे प्रदान केली जाऊ शकते.

डेटा आणि नियंत्रण प्रवाह

जेव्हा तुम्ही युनिस्वॅपच्या (Uniswap) तीन मुख्य क्रिया करता तेव्हा डेटा आणि नियंत्रणाचा प्रवाह असा असतो:

  1. वेगवेगळ्या टोकन्समध्ये अदलाबदल करणे
  2. मार्केटमध्ये तरलता जोडणे आणि जोडीच्या एक्सचेंजच्या ERC-20 तरलता टोकन्सच्या स्वरूपात बक्षीस मिळवणे
  3. ERC-20 तरलता टोकन्स जाळणे आणि जोडीचे एक्सचेंज ट्रेडर्सना ज्या ERC-20 टोकन्सची अदलाबदल करण्याची परवानगी देते ते परत मिळवणे

अदलाबदल

हा सर्वात सामान्य प्रवाह आहे, जो ट्रेडर्सद्वारे वापरला जातो:

कॉलर

  1. पेरिफेरी (periphery) खात्याला अदलाबदल करायच्या रकमेची मंजुरी द्या.
  2. पेरिफेरी कॉन्ट्रॅक्टच्या अनेक अदलाबदल फंक्शन्सपैकी एकाला कॉल करा (कोणते फंक्शन कॉल करायचे हे ETH समाविष्ट आहे की नाही, ट्रेडर जमा करायच्या टोकन्सची रक्कम निर्दिष्ट करतो की परत मिळवायच्या टोकन्सची रक्कम निर्दिष्ट करतो, इत्यादींवर अवलंबून असते). प्रत्येक अदलाबदल फंक्शन path स्वीकारते, जो पार करायच्या एक्सचेंजेसचा एक अ‍ॅरे (array) असतो.

पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)

  1. मार्गावरील प्रत्येक एक्सचेंजवर ट्रेड केल्या जाणाऱ्या रकमा ओळखा.
  2. मार्गावर पुनरावृत्ती (iterate) करते. वाटेतील प्रत्येक एक्सचेंजसाठी ते इनपुट टोकन पाठवते आणि नंतर एक्सचेंजच्या swap फंक्शनला कॉल करते. बहुतेक प्रकरणांमध्ये टोकन्ससाठी गंतव्य पत्ता हा मार्गावरील पुढील जोडीचा एक्सचेंज असतो. अंतिम एक्सचेंजमध्ये तो ट्रेडरने दिलेला पत्ता असतो.

कोअर कॉन्ट्रॅक्टमध्ये (UniswapV2Pair.sol)

  1. कोअर कॉन्ट्रॅक्टची फसवणूक होत नाहीये आणि अदलाबदलीनंतर पुरेशी तरलता राखली जाऊ शकते याची पडताळणी करा.
  2. ज्ञात राखीव साठ्याव्यतिरिक्त आपल्याकडे किती अतिरिक्त टोकन्स आहेत ते पहा. ती रक्कम म्हणजे आपल्याला एक्सचेंज करण्यासाठी मिळालेल्या इनपुट टोकन्सची संख्या आहे.
  3. आउटपुट टोकन्स गंतव्यस्थानावर पाठवा.
  4. राखीव रकमा अपडेट करण्यासाठी _update ला कॉल करा

परत पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)

  1. कोणतीही आवश्यक स्वच्छता करा (उदाहरणार्थ, ट्रेडरला पाठवण्यासाठी ETH परत मिळवण्यासाठी WETH टोकन्स जाळणे)

तरलता जोडणे

कॉलर

  1. पेरिफेरी खात्याला तरलता पूलमध्ये जोडल्या जाणाऱ्या रकमांची मंजुरी द्या.
  2. पेरिफेरी कॉन्ट्रॅक्टच्या addLiquidity फंक्शन्सपैकी एकाला कॉल करा.

पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)

  1. आवश्यक असल्यास नवीन जोडीचा एक्सचेंज तयार करा
  2. जर आधीपासूनच जोडीचा एक्सचेंज अस्तित्वात असेल, तर जोडायच्या टोकन्सच्या रकमेची गणना करा. हे दोन्ही टोकन्ससाठी समान मूल्य असणे अपेक्षित आहे, त्यामुळे नवीन टोकन्सचे विद्यमान टोकन्सशी समान गुणोत्तर असावे.
  3. रकमा स्वीकार्य आहेत का ते तपासा (कॉलर्स किमान रक्कम निर्दिष्ट करू शकतात ज्याच्या खाली ते तरलता जोडणे पसंत करणार नाहीत)
  4. कोअर कॉन्ट्रॅक्टला कॉल करा.

कोअर कॉन्ट्रॅक्टमध्ये (UniswapV2Pair.sol)

  1. तरलता टोकन्स मिंट करा आणि ते कॉलरला पाठवा
  2. राखीव रकमा अपडेट करण्यासाठी _update ला कॉल करा

तरलता काढून टाकणे

कॉलर

  1. पेरिफेरी खात्याला अंतर्निहित टोकन्सच्या बदल्यात जाळल्या जाणाऱ्या तरलता टोकन्सची मंजुरी द्या.
  2. पेरिफेरी कॉन्ट्रॅक्टच्या removeLiquidity फंक्शन्सपैकी एकाला कॉल करा.

पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)

  1. तरलता टोकन्स जोडीच्या एक्सचेंजला पाठवा

कोअर कॉन्ट्रॅक्टमध्ये (UniswapV2Pair.sol)

  1. गंतव्य पत्त्यावर जाळलेल्या टोकन्सच्या प्रमाणात अंतर्निहित टोकन्स पाठवा. उदाहरणार्थ, जर पूलमध्ये 1000 A टोकन्स, 500 B टोकन्स आणि 90 तरलता टोकन्स असतील आणि आपल्याला जाळण्यासाठी 9 टोकन्स मिळाले, तर आपण 10% तरलता टोकन्स जाळत आहोत, त्यामुळे आपण वापरकर्त्याला 100 A टोकन्स आणि 50 B टोकन्स परत पाठवतो.
  2. तरलता टोकन्स जाळा
  3. राखीव रकमा अपडेट करण्यासाठी _update ला कॉल करा

मुख्य कॉन्ट्रॅक्ट्स

हे सुरक्षित कॉन्ट्रॅक्ट्स आहेत जे तरलता धारण करतात.

UniswapV2Pair.sol

हे कॉन्ट्रॅक्ट (opens in a new tab) टोकन्सची अदलाबदल करणारा प्रत्यक्ष पूल लागू करते. ही मुख्य युनिस्वॅप कार्यक्षमता आहे.

हे सर्व इंटरफेसेस आहेत ज्यांची माहिती कॉन्ट्रॅक्टला असणे आवश्यक आहे, एकतर कारण कॉन्ट्रॅक्ट त्यांची अंमलबजावणी करते (IUniswapV2Pair आणि UniswapV2ERC20) किंवा कारण ते त्यांची अंमलबजावणी करणाऱ्या कॉन्ट्रॅक्ट्सना कॉल करते.

contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {

हे कॉन्ट्रॅक्ट UniswapV2ERC20 कडून इनहेरिट करते, जे तरलता टोकन्ससाठी ERC-20 फंक्शन्स प्रदान करते.

    using SafeMath  for uint;

ओव्हरफ्लो आणि अंडरफ्लो टाळण्यासाठी SafeMath लायब्ररी (opens in a new tab) वापरली जाते. हे महत्त्वाचे आहे कारण अन्यथा अशी परिस्थिती उद्भवू शकते जिथे मूल्य -1 असायला हवे, परंतु त्याऐवजी ते 2^256-1 असते.

    using UQ112x112 for uint224;

पूल कॉन्ट्रॅक्टमधील अनेक गणनेसाठी अपूर्णांकांची आवश्यकता असते. तथापि, EVM द्वारे अपूर्णांकांना समर्थन दिले जात नाही. युनिस्वॅपने शोधलेला उपाय म्हणजे 224 बिट मूल्यांचा वापर करणे, ज्यामध्ये पूर्णांक भागासाठी 112 बिट्स आणि अपूर्णांकासाठी 112 बिट्स असतात. त्यामुळे 1.0 हे 2^112 म्हणून दर्शविले जाते, 1.5 हे 2^112 + 2^111 म्हणून दर्शविले जाते, इ.

या लायब्ररीबद्दल अधिक तपशील दस्तऐवजात पुढे उपलब्ध आहेत.

व्हेरिएबल्स

    uint public constant MINIMUM_LIQUIDITY = 10**3;

शून्याने भाग जाण्याची प्रकरणे टाळण्यासाठी, नेहमी अस्तित्वात असलेल्या तरलता टोकन्सची किमान संख्या असते (परंतु ती शून्य खात्याच्या मालकीची असतात). ती संख्या MINIMUM_LIQUIDITY आहे, एक हजार.

    bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));

हा ERC-20 हस्तांतरण फंक्शनसाठी ABI सिलेक्टर आहे. याचा वापर दोन टोकन खात्यांमध्ये ERC-20 टोकन्स हस्तांतरित करण्यासाठी केला जातो.

    address public factory;

हे फॅक्टरी कॉन्ट्रॅक्ट आहे ज्याने हा पूल तयार केला आहे. प्रत्येक पूल हा दोन ERC-20 टोकन्समधील अदलाबदल आहे, फॅक्टरी हा एक मध्यवर्ती बिंदू आहे जो या सर्व पूल्सना जोडतो.

    address public token0;
    address public token1;

या पूलद्वारे अदलाबदल करता येणाऱ्या दोन प्रकारच्या ERC-20 टोकन्ससाठी कॉन्ट्रॅक्ट्सचे पत्ते येथे आहेत.

    uint112 private reserve0;           // सिंगल स्टोरेज स्लॉट वापरते, getReserves द्वारे प्रवेशयोग्य
    uint112 private reserve1;           // सिंगल स्टोरेज स्लॉट वापरते, getReserves द्वारे प्रवेशयोग्य

प्रत्येक टोकन प्रकारासाठी पूलकडे असलेले रिझर्व्ह. आम्ही असे गृहीत धरतो की दोन्ही समान मूल्याचे प्रतिनिधित्व करतात आणि म्हणून प्रत्येक token0 चे मूल्य reserve1/reserve0 token1 इतके आहे.

    uint32  private blockTimestampLast; // सिंगल स्टोरेज स्लॉट वापरते, getReserves द्वारे प्रवेशयोग्य

ज्या शेवटच्या ब्लॉकमध्ये अदलाबदल झाली त्याचा टाइमस्टॅम्प, जो वेळेनुसार विनिमय दरांचा मागोवा घेण्यासाठी वापरला जातो.

इथेरियम कॉन्ट्रॅक्ट्सच्या सर्वात मोठ्या गॅस खर्चांपैकी एक म्हणजे स्टोरेज, जे कॉन्ट्रॅक्टच्या एका कॉलपासून दुसऱ्या कॉलपर्यंत टिकून राहते. प्रत्येक स्टोरेज सेल 256 बिट्स लांब असतो. त्यामुळे तीन व्हेरिएबल्स, reserve0, reserve1, आणि blockTimestampLast, अशा प्रकारे वाटप केले जातात की एकाच स्टोरेज मूल्यामध्ये त्या तिन्हींचा समावेश होऊ शकतो (112+112+32=256).

    uint public price0CumulativeLast;
    uint public price1CumulativeLast;

हे व्हेरिएबल्स प्रत्येक टोकनसाठी (प्रत्येक एकमेकांच्या संदर्भात) एकत्रित खर्च धारण करतात. त्यांचा वापर ठराविक कालावधीत सरासरी विनिमय दर मोजण्यासाठी केला जाऊ शकतो.

    uint public kLast; // reserve0 * reserve1, सर्वात अलीकडील तरलता घटनेनंतर लगेचच

token0 आणि token1 मधील विनिमय दर ठरवण्याची पेअर एक्सचेंजची पद्धत म्हणजे व्यापारादरम्यान दोन रिझर्व्हचा गुणाकार स्थिर ठेवणे. kLast हे ते मूल्य आहे. जेव्हा तरलता प्रदाता टोकन्स जमा करतो किंवा काढतो तेव्हा ते बदलते आणि 0.3% मार्केट फीमुळे ते थोडे वाढते.

येथे एक साधे उदाहरण आहे. लक्षात घ्या की साधेपणासाठी टेबलमध्ये दशांश चिन्हानंतर फक्त तीन अंक आहेत आणि आम्ही 0.3% ट्रेडिंग फीकडे दुर्लक्ष करतो त्यामुळे आकडे अचूक नाहीत.

घटनाreserve0reserve1reserve0 * reserve1सरासरी विनिमय दर (token1 / token0)
प्रारंभिक सेटअप1,000.0001,000.0001,000,000
ट्रेडर A 47.619 token1 साठी 50 token0 ची अदलाबदल करतो1,050.000952.3811,000,0000.952
ट्रेडर B 8.984 token1 साठी 10 token0 ची अदलाबदल करतो1,060.000943.3961,000,0000.898
ट्रेडर C 34.305 token1 साठी 40 token0 ची अदलाबदल करतो1,100.000909.0901,000,0000.858
ट्रेडर D 109.01 token0 साठी 100 token1 ची अदलाबदल करतो990.9901,009.0901,000,0000.917
ट्रेडर E 10.079 token1 साठी 10 token0 ची अदलाबदल करतो1,000.990999.0101,000,0001.008

जसे ट्रेडर्स अधिक token0 प्रदान करतात, तसे पुरवठा आणि मागणीच्या आधारावर token1 चे सापेक्ष मूल्य वाढते आणि याउलट.

लॉक

    uint private unlocked = 1;

सुरक्षा भेद्यतेचा एक वर्ग आहे जो पुनर्प्रवेश गैरवापरावर (opens in a new tab) आधारित आहे. युनिस्वॅपला अनियंत्रित ERC-20 टोकन्स हस्तांतरित करण्याची आवश्यकता असते, ज्याचा अर्थ अशा ERC-20 कॉन्ट्रॅक्ट्सना कॉल करणे जे त्यांना कॉल करणाऱ्या युनिस्वॅप मार्केटचा गैरवापर करण्याचा प्रयत्न करू शकतात. कॉन्ट्रॅक्टचा भाग म्हणून unlocked व्हेरिएबल ठेवून, आपण फंक्शन्स चालू असताना (एकाच व्यवहारामध्ये) त्यांना कॉल करण्यापासून रोखू शकतो.

    modifier lock() {

हे फंक्शन एक मॉडिफायर (opens in a new tab) आहे, एक फंक्शन जे सामान्य फंक्शनच्या भोवती गुंडाळले जाते जेणेकरून त्याचे वर्तन काही प्रकारे बदलता येईल.

        require(unlocked == 1, 'UniswapV2: LOCKED');
        unlocked = 0;

जर unlocked एक च्या बरोबरीचे असेल, तर ते शून्य वर सेट करा. जर ते आधीच शून्य असेल तर कॉल पूर्ववत करा, तो अयशस्वी करा.

        _;

मॉडिफायरमध्ये _; हा मूळ फंक्शन कॉल असतो (सर्व पॅरामीटर्ससह). येथे याचा अर्थ असा आहे की फंक्शन कॉल तेव्हाच होतो जेव्हा कॉल केला गेला तेव्हा unlocked एक होता आणि ते चालू असताना unlocked चे मूल्य शून्य असते.

        unlocked = 1;
    }

मुख्य फंक्शन परत आल्यानंतर, लॉक सोडा.

संकीर्ण फंक्शन्स

    function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
        _reserve0 = reserve0;
        _reserve1 = reserve1;
        _blockTimestampLast = blockTimestampLast;
    }

हे फंक्शन कॉलर्सना एक्सचेंजची वर्तमान स्थिती प्रदान करते. लक्षात घ्या की Solidity फंक्शन्स अनेक मूल्ये परत करू शकतात (opens in a new tab).

    function _safeTransfer(address token, address to, uint value) private {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));

हे अंतर्गत फंक्शन एक्सचेंजमधून दुसऱ्या कोणालातरी ERC20 टोकन्सची रक्कम हस्तांतरित करते. SELECTOR हे निर्दिष्ट करते की आपण कॉल करत असलेले फंक्शन transfer(address,uint) आहे (वरील व्याख्या पहा).

टोकन फंक्शनसाठी इंटरफेस आयात करणे टाळण्यासाठी, आम्ही ABI फंक्शन्सपैकी (opens in a new tab) एकाचा वापर करून "मॅन्युअली" कॉल तयार करतो.

        require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
    }

ERC-20 हस्तांतरण कॉल अपयश नोंदवू शकण्याचे दोन मार्ग आहेत:

  1. पूर्ववत करणे. जर बाह्य कॉन्ट्रॅक्टचा कॉल पूर्ववत झाला, तर बुलियन रिटर्न व्हॅल्यू false असते
  2. सामान्यपणे समाप्त होणे परंतु अपयश नोंदवणे. त्या बाबतीत रिटर्न व्हॅल्यू बफरची लांबी शून्येतर असते आणि जेव्हा बुलियन व्हॅल्यू म्हणून डीकोड केले जाते तेव्हा ते false असते

यापैकी कोणतीही परिस्थिती उद्भवल्यास, पूर्ववत करा.

घटना

    event Mint(address indexed sender, uint amount0, uint amount1);
    event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);

जेव्हा तरलता प्रदाता एकतर तरलता जमा करतो (Mint) किंवा ती काढतो (Burn) तेव्हा या दोन घटना उत्सर्जित केल्या जातात. दोन्ही प्रकरणांमध्ये, जमा केलेल्या किंवा काढलेल्या token0 आणि token1 ची रक्कम घटनेचा भाग असते, तसेच आम्हाला कॉल करणाऱ्या खात्याची ओळख (sender) देखील असते. रक्कम काढण्याच्या बाबतीत, घटनेमध्ये टोकन्स प्राप्त करणारे लक्ष्य (to) देखील समाविष्ट असते, जे प्रेषकासारखे नसू शकते.

    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );

जेव्हा एखादा ट्रेडर एका टोकनची दुसऱ्या टोकनसाठी अदलाबदल करतो तेव्हा ही घटना उत्सर्जित केली जाते. पुन्हा, प्रेषक आणि गंतव्यस्थान समान नसू शकतात. प्रत्येक टोकन एकतर एक्सचेंजला पाठवले जाऊ शकते किंवा त्यातून प्राप्त केले जाऊ शकते.

    event Sync(uint112 reserve0, uint112 reserve1);

शेवटी, नवीनतम रिझर्व्ह माहिती (आणि म्हणून विनिमय दर) प्रदान करण्यासाठी, कारण काहीही असले तरी, प्रत्येक वेळी टोकन्स जोडले किंवा काढले जातात तेव्हा Sync उत्सर्जित केले जाते.

सेटअप फंक्शन्स

जेव्हा नवीन पेअर एक्सचेंज सेट केले जाते तेव्हा या फंक्शन्सना एकदा कॉल करणे अपेक्षित असते.

    constructor() public {
        factory = msg.sender;
    }

कन्स्ट्रक्टर हे सुनिश्चित करतो की आम्ही पेअर तयार करणाऱ्या फॅक्टरीच्या पत्त्याचा मागोवा ठेवू. ही माहिती initialize आणि फॅक्टरी फीसाठी (जर अस्तित्वात असेल तर) आवश्यक आहे

    // डिप्लॉयमेंटच्या वेळी फॅक्टरीद्वारे एकदाच कॉल केले जाते
    function initialize(address _token0, address _token1) external {
        require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // पुरेशी तपासणी
        token0 = _token0;
        token1 = _token1;
    }

हे फंक्शन फॅक्टरीला (आणि फक्त फॅक्टरीला) दोन ERC-20 टोकन्स निर्दिष्ट करण्याची परवानगी देते ज्यांची ही पेअर अदलाबदल करेल.

अंतर्गत अपडेट फंक्शन्स

_update
    // रिझर्व्ह अपडेट करा आणि, प्रति ब्लॉक पहिल्या कॉलवर, प्राईस अ‍ॅक्युम्युलेटर्स
    function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {

प्रत्येक वेळी टोकन्स जमा केल्यावर किंवा काढल्यावर या फंक्शनला कॉल केला जातो.

        require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');

जर balance0 किंवा balance1 (uint256) पैकी कोणतेही uint112(-1) (=2^112-1) पेक्षा जास्त असेल (त्यामुळे ते ओव्हरफ्लो होते आणि uint112 मध्ये रूपांतरित केल्यावर 0 वर परत येते) तर ओव्हरफ्लो टाळण्यासाठी _update सुरू ठेवण्यास नकार द्या. 10^18 युनिट्समध्ये विभागल्या जाऊ शकणाऱ्या सामान्य टोकनसह, याचा अर्थ प्रत्येक एक्सचेंज प्रत्येक टोकनच्या सुमारे 5.1*10^15 पर्यंत मर्यादित आहे. आतापर्यंत ही समस्या उद्भवलेली नाही.

        uint32 blockTimestamp = uint32(block.timestamp % 2**32);
        uint32 timeElapsed = blockTimestamp - blockTimestampLast; // ओव्हरफ्लो अपेक्षित आहे
        if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {

जर गेलेला वेळ शून्य नसेल, तर याचा अर्थ असा की आम्ही या ब्लॉकवरील पहिला एक्सचेंज व्यवहार आहोत. त्या बाबतीत, आम्हाला कॉस्ट ॲक्युम्युलेटर्स अपडेट करणे आवश्यक आहे.

            // * कधीही ओव्हरफ्लो होत नाही, आणि + ओव्हरफ्लो अपेक्षित आहे
            price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
            price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
        }

प्रत्येक कॉस्ट ॲक्युम्युलेटर नवीनतम कॉस्ट (दुसऱ्या टोकनचा रिझर्व्ह/या टोकनचा रिझर्व्ह) गुणिले सेकंदांमध्ये गेलेला वेळ यासह अपडेट केला जातो. सरासरी किंमत मिळवण्यासाठी, तुम्ही वेळेच्या दोन बिंदूंमधील एकत्रित किंमत वाचता आणि त्यांच्यातील वेळेच्या फरकाने भागता. उदाहरणार्थ, घटनांचा हा क्रम गृहीत धरा:

घटनाreserve0reserve1टाइमस्टॅम्पकिरकोळ विनिमय दर (reserve1 / reserve0)price0CumulativeLast
प्रारंभिक सेटअप1,000.0001,000.0005,0001.0000
ट्रेडर A 50 token0 जमा करतो आणि 47.619 token1 परत मिळवतो1,050.000952.3815,0200.90720
ट्रेडर B 10 token0 जमा करतो आणि 8.984 token1 परत मिळवतो1,060.000943.3965,0300.89020+10*0.907 = 29.07
ट्रेडर C 40 token0 जमा करतो आणि 34.305 token1 परत मिळवतो1,100.000909.0905,1000.82629.07+70*0.890 = 91.37
ट्रेडर D 100 token1 जमा करतो आणि 109.01 token0 परत मिळवतो990.9901,009.0905,1101.01891.37+10*0.826 = 99.63
ट्रेडर E 10 token0 जमा करतो आणि 10.079 token1 परत मिळवतो1,000.990999.0105,1500.99899.63+40*1.1018 = 143.702

समजा आपल्याला 5,030 आणि 5,150 या टाइमस्टॅम्प्स दरम्यान Token0 ची सरासरी किंमत मोजायची आहे. price0Cumulative च्या मूल्यातील फरक 143.702-29.07=114.632 आहे. ही दोन मिनिटांची (120 सेकंद) सरासरी आहे. त्यामुळे सरासरी किंमत 114.632/120 = 0.955 आहे.

या किंमतीच्या गणनेमुळेच आपल्याला जुन्या रिझर्व्ह आकारांची माहिती असणे आवश्यक आहे.

        reserve0 = uint112(balance0);
        reserve1 = uint112(balance1);
        blockTimestampLast = blockTimestamp;
        emit Sync(reserve0, reserve1);
    }

शेवटी, ग्लोबल व्हेरिएबल्स अपडेट करा आणि Sync घटना उत्सर्जित करा.

_mintFee
    // जर फी चालू असेल, तर sqrt(k) मधील वाढीच्या 1/6 च्या समतुल्य तरलता मिंट करा
    function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {

युनिस्वॅप 2.0 मध्ये ट्रेडर्स मार्केट वापरण्यासाठी 0.30% फी भरतात. त्या फीचा बहुतांश भाग (ट्रेडचा 0.25%) नेहमी तरलता प्रदात्यांना जातो. उर्वरित 0.05% एकतर तरलता प्रदात्यांना किंवा फॅक्टरीद्वारे प्रोटोकॉल फी म्हणून निर्दिष्ट केलेल्या पत्त्यावर जाऊ शकतो, जो युनिस्वॅपला त्यांच्या विकास प्रयत्नांसाठी पैसे देतो.

गणने कमी करण्यासाठी (आणि त्यामुळे गॅस खर्च), ही फी प्रत्येक व्यवहाराऐवजी केवळ तेव्हाच मोजली जाते जेव्हा पूलमधून तरलता जोडली जाते किंवा काढली जाते.

        address feeTo = IUniswapV2Factory(factory).feeTo();
        feeOn = feeTo != address(0);

फॅक्टरीचे फी गंतव्यस्थान वाचा. जर ते शून्य असेल तर कोणतीही प्रोटोकॉल फी नाही आणि ती फी मोजण्याची गरज नाही.

        uint _kLast = kLast; // गॅस बचत

kLast स्थिती व्हेरिएबल स्टोरेजमध्ये स्थित आहे, त्यामुळे कॉन्ट्रॅक्टच्या वेगवेगळ्या कॉल्स दरम्यान त्याचे मूल्य असेल. स्टोरेजमध्ये प्रवेश करणे हे कॉन्ट्रॅक्टचा फंक्शन कॉल संपल्यावर सोडल्या जाणाऱ्या अस्थिर मेमरीमध्ये प्रवेश करण्यापेक्षा खूप महाग आहे, त्यामुळे गॅस वाचवण्यासाठी आम्ही अंतर्गत व्हेरिएबल वापरतो.

        if (feeOn) {
            if (_kLast != 0) {

तरलता प्रदात्यांना त्यांचा वाटा केवळ त्यांच्या तरलता टोकन्सच्या वाढीमुळे मिळतो. परंतु प्रोटोकॉल फीसाठी नवीन तरलता टोकन्स मिंट करणे आणि feeTo पत्त्यावर प्रदान करणे आवश्यक आहे.

                uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
                uint rootKLast = Math.sqrt(_kLast);
                if (rootK > rootKLast) {

जर नवीन तरलता असेल ज्यावर प्रोटोकॉल फी गोळा करायची असेल. तुम्ही वर्गमूळ फंक्शन या लेखात पुढे पाहू शकता

                    uint numerator = totalSupply.mul(rootK.sub(rootKLast));
                    uint denominator = rootK.mul(5).add(rootKLast);
                    uint liquidity = numerator / denominator;

फीची ही गुंतागुंतीची गणना श्वेतपत्रिकेच्या (opens in a new tab) पृष्ठ 5 वर स्पष्ट केली आहे. आम्हाला माहित आहे की kLast मोजल्या गेलेल्या वेळेपासून ते आतापर्यंत कोणतीही तरलता जोडली किंवा काढली गेली नाही (कारण आम्ही प्रत्येक वेळी तरलता जोडली किंवा काढली जाते तेव्हा, ती प्रत्यक्षात बदलण्यापूर्वी ही गणना चालवतो), त्यामुळे reserve0 * reserve1 मधील कोणताही बदल व्यवहार फीमधून आला पाहिजे (त्यांच्याशिवाय आम्ही reserve0 * reserve1 स्थिर ठेवू).

                    if (liquidity > 0) _mint(feeTo, liquidity);
                }
            }

अतिरिक्त तरलता टोकन्स प्रत्यक्षात तयार करण्यासाठी आणि त्यांना feeTo ला नियुक्त करण्यासाठी UniswapV2ERC20._mint फंक्शन वापरा.

        } else if (_kLast != 0) {
            kLast = 0;
        }
    }

जर कोणतीही फी नसेल तर kLast शून्य वर सेट करा (जर ते आधीच नसेल). जेव्हा हे कॉन्ट्रॅक्ट लिहिले गेले तेव्हा एक गॅस परतावा वैशिष्ट्य (opens in a new tab) होते ज्याने कॉन्ट्रॅक्ट्सना त्यांना आवश्यक नसलेले स्टोरेज शून्य करून इथेरियम स्थितीचा एकूण आकार कमी करण्यास प्रोत्साहित केले. शक्य असेल तेव्हा या कोडला तो परतावा मिळतो.

बाह्यरित्या प्रवेशयोग्य फंक्शन्स

लक्षात घ्या की कोणताही व्यवहार किंवा कॉन्ट्रॅक्ट या फंक्शन्सना कॉल करू शकतो, तरीही ते पेरिफेरी कॉन्ट्रॅक्टमधून कॉल करण्यासाठी डिझाइन केलेले आहेत. जर तुम्ही त्यांना थेट कॉल केले तर तुम्ही पेअर एक्सचेंजची फसवणूक करू शकणार नाही, परंतु चुकीमुळे तुमचे मूल्य गमावू शकता.

मिंट
    // हे लो-लेव्हल फंक्शन अशा कॉन्ट्रॅक्टमधून कॉल केले जावे जे महत्त्वाच्या सुरक्षितता तपासण्या करते
    function mint(address to) external lock returns (uint liquidity) {

जेव्हा तरलता प्रदाता पूलमध्ये तरलता जोडतो तेव्हा या फंक्शनला कॉल केला जातो. हे बक्षीस म्हणून अतिरिक्त तरलता टोकन्स मिंट करते. याला एका पेरिफेरी कॉन्ट्रॅक्टमधून कॉल केले जावे जे एकाच व्यवहारामध्ये तरलता जोडल्यानंतर त्याला कॉल करते (जेणेकरून कायदेशीर मालकापूर्वी नवीन तरलतेवर दावा करणारा व्यवहार इतर कोणीही सबमिट करू शकणार नाही).

        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // गॅस बचत

अनेक मूल्ये परत करणाऱ्या Solidity फंक्शनचे परिणाम वाचण्याचा हा मार्ग आहे. आम्ही शेवटची परत केलेली मूल्ये, ब्लॉक टाइमस्टॅम्प टाकून देतो, कारण आम्हाला त्याची आवश्यकता नाही.

        uint balance0 = IERC20(token0).balanceOf(address(this));
        uint balance1 = IERC20(token1).balanceOf(address(this));
        uint amount0 = balance0.sub(_reserve0);
        uint amount1 = balance1.sub(_reserve1);

सध्याचे बॅलन्स मिळवा आणि प्रत्येक टोकन प्रकाराचे किती जोडले गेले ते पहा.

        bool feeOn = _mintFee(_reserve0, _reserve1);

गोळा करण्यासाठी प्रोटोकॉल फीची गणना करा, जर काही असेल तर, आणि त्यानुसार तरलता टोकन्स मिंट करा. कारण _mintFee चे पॅरामीटर्स जुनी रिझर्व्ह मूल्ये आहेत, फीची गणना केवळ फीमुळे होणाऱ्या पूल बदलांवर आधारित अचूकपणे केली जाते.

        uint _totalSupply = totalSupply; // गॅस बचत, येथे परिभाषित करणे आवश्यक आहे कारण totalSupply _mintFee मध्ये अपडेट होऊ शकते
        if (_totalSupply == 0) {
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(address(0), MINIMUM_LIQUIDITY); // पहिले MINIMUM_LIQUIDITY टोकन्स कायमचे लॉक करा

जर ही पहिली ठेव असेल, तर MINIMUM_LIQUIDITY टोकन्स तयार करा आणि त्यांना लॉक करण्यासाठी शून्य पत्त्यावर पाठवा. ते कधीही रिडीम केले जाऊ शकत नाहीत, ज्याचा अर्थ पूल कधीही पूर्णपणे रिकामा होणार नाही (हे आम्हाला काही ठिकाणी शून्याने भाग जाण्यापासून वाचवते). MINIMUM_LIQUIDITY चे मूल्य एक हजार आहे, जे बहुतेक ERC-20 टोकनच्या 10^-18 व्या युनिट्समध्ये विभागलेले आहेत हे लक्षात घेता, जसे ETH Wei मध्ये विभागले गेले आहे, एकाच टोकनच्या मूल्याच्या 10^-15 आहे. जास्त किंमत नाही.

पहिल्या ठेवीच्या वेळी आम्हाला दोन टोकन्सचे सापेक्ष मूल्य माहित नसते, त्यामुळे आम्ही फक्त रकमांचा गुणाकार करतो आणि वर्गमूळ काढतो, असे गृहीत धरून की ठेव आम्हाला दोन्ही टोकन्समध्ये समान मूल्य प्रदान करते.

आम्ही यावर विश्वास ठेवू शकतो कारण आर्बिट्रेजमुळे मूल्य गमावणे टाळण्यासाठी समान मूल्य प्रदान करणे हे ठेवीदाराच्या हिताचे आहे. समजा दोन टोकन्सचे मूल्य समान आहे, परंतु आमच्या ठेवीदाराने Token0 च्या तुलनेत Token1 ची चार पट जास्त ठेव ठेवली आहे. पेअर एक्सचेंजला असे वाटते की Token0 अधिक मौल्यवान आहे या वस्तुस्थितीचा वापर करून एखादा ट्रेडर त्यातून मूल्य काढू शकतो.

घटनाreserve0reserve1reserve0 * reserve1पूलचे मूल्य (reserve0 + reserve1)
प्रारंभिक सेटअप83225640
ट्रेडर 8 Token0 टोकन्स जमा करतो, 16 Token1 परत मिळवतो161625632

जसे तुम्ही पाहू शकता, ट्रेडरने अतिरिक्त 8 टोकन्स मिळवले, जे पूलच्या मूल्यातील कपातीतून येतात, ज्यामुळे त्याच्या मालक असलेल्या ठेवीदाराचे नुकसान होते.

        } else {
            liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);

प्रत्येक त्यानंतरच्या ठेवीसह आम्हाला दोन मालमत्तांमधील विनिमय दर आधीच माहित असतो आणि आम्ही तरलता प्रदात्यांनी दोन्हीमध्ये समान मूल्य प्रदान करण्याची अपेक्षा करतो. जर त्यांनी तसे केले नाही, तर आम्ही त्यांना शिक्षा म्हणून त्यांनी प्रदान केलेल्या कमी मूल्यावर आधारित तरलता टोकन्स देतो.

ती प्रारंभिक ठेव असो किंवा त्यानंतरची, आम्ही प्रदान करत असलेल्या तरलता टोकन्सची संख्या reserve0*reserve1 मधील बदलाच्या वर्गमुळाच्या बरोबरीची असते आणि तरलता टोकनचे मूल्य बदलत नाही (जोपर्यंत आम्हाला अशी ठेव मिळत नाही ज्यामध्ये दोन्ही प्रकारांची समान मूल्ये नाहीत, अशा परिस्थितीत "दंड" वितरित केला जातो). येथे समान मूल्य असलेल्या दोन टोकन्सचे आणखी एक उदाहरण आहे, ज्यामध्ये तीन चांगल्या ठेवी आणि एक वाईट ठेव आहे (केवळ एका टोकन प्रकाराची ठेव, त्यामुळे ती कोणतीही तरलता टोकन्स तयार करत नाही).

घटनाreserve0reserve1reserve0 * reserve1पूल मूल्य (reserve0 + reserve1)या ठेवीसाठी मिंट केलेले तरलता टोकन्सएकूण तरलता टोकन्सप्रत्येक तरलता टोकनचे मूल्य
प्रारंभिक सेटअप8.0008.0006416.000882.000
प्रत्येक प्रकाराचे चार जमा करा12.00012.00014424.0004122.000
प्रत्येक प्रकाराचे दोन जमा करा14.00014.00019628.0002142.000
असमान मूल्याची ठेव18.00014.00025232.000014~2.286
आर्बिट्रेजनंतर~15.874~15.874252~31.748014~2.267
        }
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        _mint(to, liquidity);

अतिरिक्त तरलता टोकन्स प्रत्यक्षात तयार करण्यासाठी आणि त्यांना योग्य खात्यात देण्यासाठी UniswapV2ERC20._mint फंक्शन वापरा.


        _update(balance0, balance1, _reserve0, _reserve1);
        if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 आणि reserve1 अद्ययावत आहेत
        emit Mint(msg.sender, amount0, amount1);
    }

स्थिती व्हेरिएबल्स अपडेट करा (reserve0, reserve1, आणि आवश्यक असल्यास kLast) आणि योग्य घटना उत्सर्जित करा.

जाळणे
    // हे लो-लेव्हल फंक्शन अशा कॉन्ट्रॅक्टमधून कॉल केले जावे जे महत्त्वाच्या सुरक्षितता तपासण्या करते
    function burn(address to) external lock returns (uint amount0, uint amount1) {

जेव्हा तरलता काढली जाते आणि योग्य तरलता टोकन्स जाळणे आवश्यक असते तेव्हा या फंक्शनला कॉल केला जातो. याला पेरिफेरी खात्यातून देखील कॉल केले जावे.

        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // गॅस बचत
        address _token0 = token0;                                // गॅस बचत
        address _token1 = token1;                                // गॅस बचत
        uint balance0 = IERC20(_token0).balanceOf(address(this));
        uint balance1 = IERC20(_token1).balanceOf(address(this));
        uint liquidity = balanceOf[address(this)];

पेरिफेरी कॉन्ट्रॅक्टने कॉल करण्यापूर्वी जाळायची तरलता या कॉन्ट्रॅक्टमध्ये हस्तांतरित केली. अशा प्रकारे आम्हाला माहित असते की किती तरलता जाळायची आहे आणि आम्ही ती जाळली जाईल याची खात्री करू शकतो.

        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint _totalSupply = totalSupply; // गॅस बचत, येथे परिभाषित करणे आवश्यक आहे कारण totalSupply _mintFee मध्ये अपडेट होऊ शकते
        amount0 = liquidity.mul(balance0) / _totalSupply; // बॅलन्स वापरल्याने प्रमाणबद्ध (pro-rata) वितरण सुनिश्चित होते
        amount1 = liquidity.mul(balance1) / _totalSupply; // बॅलन्स वापरल्याने प्रमाणबद्ध (pro-rata) वितरण सुनिश्चित होते
        require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');

तरलता प्रदात्याला दोन्ही टोकन्सचे समान मूल्य मिळते. अशा प्रकारे आम्ही विनिमय दर बदलत नाही.

उर्वरित burn फंक्शन हे वरील mint फंक्शनची आरशातील प्रतिमा आहे.

अदलाबदल
    // हे लो-लेव्हल फंक्शन अशा कॉन्ट्रॅक्टमधून कॉल केले जावे जे महत्त्वाच्या सुरक्षितता तपासण्या करते
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {

या फंक्शनला देखील एका पेरिफेरी कॉन्ट्रॅक्टमधून कॉल करणे अपेक्षित आहे.

        require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // गॅस बचत
        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

        uint balance0;
        uint balance1;
        { // _token{0,1} साठी स्कोप, स्टॅक टू डीप (stack too deep) त्रुटी टाळतो

स्थानिक व्हेरिएबल्स एकतर मेमरीमध्ये किंवा, जर ते खूप जास्त नसतील तर, थेट स्टॅकवर साठवले जाऊ शकतात. जर आपण संख्या मर्यादित करू शकलो जेणेकरून आपण स्टॅक वापरू, तर आपण कमी गॅस वापरू. अधिक तपशीलांसाठी येलो पेपर, औपचारिक इथेरियम तपशील (opens in a new tab), पृष्ठ 26, समीकरण 298 पहा.

            address _token0 = token0;
            address _token1 = token1;
            require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
            if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // आशावादीपणे टोकन्सचे हस्तांतरण करा
            if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // आशावादीपणे टोकन्सचे हस्तांतरण करा

हे हस्तांतरण आशावादी आहे, कारण सर्व अटी पूर्ण झाल्याची खात्री होण्यापूर्वी आम्ही हस्तांतरण करतो. इथेरियममध्ये हे ठीक आहे कारण जर कॉलमध्ये नंतर अटी पूर्ण झाल्या नाहीत तर आम्ही त्यातून आणि त्याने तयार केलेल्या कोणत्याही बदलांमधून पूर्ववत करतो.

            if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);

विनंती केल्यास प्राप्तकर्त्याला अदलाबदलीबद्दल माहिती द्या.

            balance0 = IERC20(_token0).balanceOf(address(this));
            balance1 = IERC20(_token1).balanceOf(address(this));
        }

सध्याचे बॅलन्स मिळवा. अदलाबदलीसाठी आम्हाला कॉल करण्यापूर्वी पेरिफेरी कॉन्ट्रॅक्ट आम्हाला टोकन्स पाठवते. यामुळे कॉन्ट्रॅक्टला त्याची फसवणूक होत नाही हे तपासणे सोपे होते, एक तपासणी जी मुख्य कॉन्ट्रॅक्टमध्ये होणे आवश्यक आहे (कारण आम्हाला आमच्या पेरिफेरी कॉन्ट्रॅक्ट व्यतिरिक्त इतर संस्थांद्वारे कॉल केले जाऊ शकते).

        uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
        uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
        require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
        { // reserve{0,1}Adjusted साठी स्कोप, स्टॅक टू डीप (stack too deep) त्रुटी टाळतो
            uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
            uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
            require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');

अदलाबदलीतून आमचे नुकसान होणार नाही याची खात्री करण्यासाठी ही एक सॅनिटी चेक आहे. अशी कोणतीही परिस्थिती नाही ज्यामध्ये अदलाबदलीने reserve0*reserve1 कमी केले पाहिजे. येथेच आम्ही हे देखील सुनिश्चित करतो की अदलाबदलीवर 0.3% फी पाठविली जात आहे; K च्या मूल्याची सॅनिटी तपासणी करण्यापूर्वी, आम्ही दोन्ही बॅलन्सना 1000 ने गुणतो आणि त्यातून 3 ने गुणलेल्या रकमा वजा करतो, याचा अर्थ K मूल्याची सध्याच्या रिझर्व्हच्या K मूल्याशी तुलना करण्यापूर्वी बॅलन्समधून 0.3% (3/1000 = 0.003 = 0.3%) वजा केले जात आहेत.

        }

        _update(balance0, balance1, _reserve0, _reserve1);
        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
    }

reserve0 आणि reserve1 अपडेट करा, आणि आवश्यक असल्यास किंमत ॲक्युम्युलेटर्स आणि टाइमस्टॅम्प अपडेट करा आणि एक घटना उत्सर्जित करा.

समक्रमण किंवा स्किम

पेअर एक्सचेंजला वाटत असलेल्या रिझर्व्हच्या तुलनेत वास्तविक बॅलन्स समक्रमणाबाहेर जाणे शक्य आहे. कॉन्ट्रॅक्टच्या संमतीशिवाय टोकन्स काढण्याचा कोणताही मार्ग नाही, परंतु ठेवी ही वेगळी बाब आहे. एखादे खाते mint किंवा swap पैकी कोणालाही कॉल न करता एक्सचेंजमध्ये टोकन्स हस्तांतरित करू शकते.

त्या बाबतीत दोन उपाय आहेत:

  • sync, रिझर्व्ह सध्याच्या बॅलन्सवर अपडेट करा
  • skim, अतिरिक्त रक्कम काढा. लक्षात घ्या की कोणत्याही खात्याला skim कॉल करण्याची परवानगी आहे कारण आम्हाला माहित नाही की टोकन्स कोणी जमा केले. ही माहिती एका घटनेमध्ये उत्सर्जित केली जाते, परंतु घटना ब्लॉकचेनवरून प्रवेशयोग्य नसतात.

UniswapV2Factory.sol

हे कॉन्ट्रॅक्ट (opens in a new tab) पेअर एक्सचेंजेस तयार करते.

pragma solidity =0.5.16;

import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';

contract UniswapV2Factory is IUniswapV2Factory {
    address public feeTo;
    address public feeToSetter;

प्रोटोकॉल फी लागू करण्यासाठी हे स्थिती व्हेरिएबल्स आवश्यक आहेत (श्वेतपत्रिका (opens in a new tab), पृष्ठ 5 पहा). feeTo पत्ता प्रोटोकॉल फीसाठी तरलता टोकन्स जमा करतो आणि feeToSetter हा feeTo ला वेगळ्या पत्त्यावर बदलण्याची परवानगी असलेला पत्ता आहे.

    mapping(address => mapping(address => address)) public getPair;
    address[] public allPairs;

हे व्हेरिएबल्स पेअर्सचा, दोन टोकन प्रकारांमधील एक्सचेंजेसचा मागोवा ठेवतात.

पहिले, getPair, एक मॅपिंग आहे जे पेअर एक्सचेंज कॉन्ट्रॅक्टची ओळख ते अदलाबदल करत असलेल्या दोन ERC-20 टोकन्सच्या आधारे करते. ERC-20 टोकन्सची ओळख त्यांची अंमलबजावणी करणाऱ्या कॉन्ट्रॅक्ट्सच्या पत्त्यांद्वारे केली जाते, त्यामुळे कीज आणि मूल्य हे सर्व पत्ते आहेत. tokenA मधून tokenB मध्ये रूपांतरित करू देणाऱ्या पेअर एक्सचेंजचा पत्ता मिळवण्यासाठी, तुम्ही getPair[<tokenA address>][<tokenB address>] वापरता (किंवा उलट).

दुसरे व्हेरिएबल, allPairs, एक ॲरे आहे ज्यामध्ये या फॅक्टरीने तयार केलेल्या पेअर एक्सचेंजेसचे सर्व पत्ते समाविष्ट आहेत. इथेरियममध्ये तुम्ही मॅपिंगच्या सामग्रीवर पुनरावृत्ती करू शकत नाही किंवा सर्व कीजची सूची मिळवू शकत नाही, त्यामुळे ही फॅक्टरी कोणते एक्सचेंजेस व्यवस्थापित करते हे जाणून घेण्याचा हा व्हेरिएबल एकमेव मार्ग आहे.

टीप: तुम्ही मॅपिंगच्या सर्व कीजवर पुनरावृत्ती करू शकत नाही याचे कारण असे आहे की कॉन्ट्रॅक्ट डेटा स्टोरेज महाग आहे, त्यामुळे आपण त्याचा जितका कमी वापर करू तितके चांगले आणि आपण ते जितक्या कमी वेळा बदलू तितके चांगले. तुम्ही पुनरावृत्तीला समर्थन देणारी मॅपिंग्ज (opens in a new tab) तयार करू शकता, परंतु त्यांना कीजच्या सूचीसाठी अतिरिक्त स्टोरेजची आवश्यकता असते. बहुतेक ॲप्लिकेशन्समध्ये तुम्हाला त्याची आवश्यकता नसते.

    event PairCreated(address indexed token0, address indexed token1, address pair, uint);

जेव्हा नवीन पेअर एक्सचेंज तयार केले जाते तेव्हा ही घटना उत्सर्जित केली जाते. यामध्ये टोकन्सचे पत्ते, पेअर एक्सचेंजचा पत्ता आणि फॅक्टरीद्वारे व्यवस्थापित केलेल्या एक्सचेंजेसची एकूण संख्या समाविष्ट असते.

    constructor(address _feeToSetter) public {
        feeToSetter = _feeToSetter;
    }

कन्स्ट्रक्टर फक्त एकच गोष्ट करतो ती म्हणजे feeToSetter निर्दिष्ट करणे. फॅक्टरीज फीशिवाय सुरू होतात आणि फक्त feeSetter ते बदलू शकतो.

    function allPairsLength() external view returns (uint) {
        return allPairs.length;
    }

हे फंक्शन एक्सचेंज पेअर्सची संख्या परत करते.

    function createPair(address tokenA, address tokenB) external returns (address pair) {

दोन ERC-20 टोकन्समधील पेअर एक्सचेंज तयार करणे हे फॅक्टरीचे मुख्य फंक्शन आहे. लक्षात घ्या की कोणीही या फंक्शनला कॉल करू शकतो. नवीन पेअर एक्सचेंज तयार करण्यासाठी तुम्हाला युनिस्वॅपकडून परवानगीची आवश्यकता नाही.

        require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);

आम्हाला नवीन एक्सचेंजचा पत्ता निश्चित असावा असे वाटते, जेणेकरून त्याची आगाऊ साखळीबाह्य गणना केली जाऊ शकेल (हे स्तर २ (l2) व्यवहारांसाठी उपयुक्त ठरू शकते). हे करण्यासाठी आम्हाला टोकन पत्त्यांचा एक सुसंगत क्रम असणे आवश्यक आहे, मग ते आम्हाला कोणत्याही क्रमाने प्राप्त झाले असले तरीही, त्यामुळे आम्ही त्यांना येथे क्रमवारी लावतो.

        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
        require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // एकच तपासणी पुरेशी आहे

मोठे तरलता पूल लहान पूल्सपेक्षा चांगले असतात, कारण त्यांच्या किंमती अधिक स्थिर असतात. आम्हाला टोकन्सच्या प्रति पेअर एकापेक्षा जास्त तरलता पूल नको आहे. जर आधीच एखादे एक्सचेंज असेल, तर त्याच पेअरसाठी दुसरे तयार करण्याची गरज नाही.

        bytes memory bytecode = type(UniswapV2Pair).creationCode;

नवीन कॉन्ट्रॅक्ट तयार करण्यासाठी आम्हाला ते तयार करणारा कोड आवश्यक आहे (कन्स्ट्रक्टर फंक्शन आणि प्रत्यक्ष कॉन्ट्रॅक्टचा EVM बाइटकोड मेमरीमध्ये लिहिणारा कोड दोन्ही). साधारणपणे Solidity मध्ये आम्ही फक्त addr = new <name of contract>(<constructor parameters>) वापरतो आणि कंपायलर आमच्यासाठी सर्व गोष्टींची काळजी घेतो, परंतु निश्चित कॉन्ट्रॅक्ट पत्ता मिळवण्यासाठी आम्हाला CREATE2 ऑपकोड (opens in a new tab) वापरण्याची आवश्यकता आहे. जेव्हा हा कोड लिहिला गेला तेव्हा त्या ऑपकोडला Solidity द्वारे अद्याप समर्थन दिले गेले नव्हते, त्यामुळे मॅन्युअली कोड मिळवणे आवश्यक होते. ही आता समस्या राहिलेली नाही, कारण Solidity आता CREATE2 ला समर्थन देते (opens in a new tab).

        bytes32 salt = keccak256(abi.encodePacked(token0, token1));
        assembly {
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }

जेव्हा एखाद्या ऑपकोडला Solidity द्वारे अद्याप समर्थन दिले जात नाही तेव्हा आम्ही इनलाइन असेंब्ली (opens in a new tab) वापरून त्याला कॉल करू शकतो.

        IUniswapV2Pair(pair).initialize(token0, token1);

नवीन एक्सचेंजला ते कोणत्या दोन टोकन्सची अदलाबदल करते हे सांगण्यासाठी initialize फंक्शनला कॉल करा.

        getPair[token0][token1] = pair;
        getPair[token1][token0] = pair; // उलट दिशेने मॅपिंग पॉप्युलेट करा
        allPairs.push(pair);
        emit PairCreated(token0, token1, pair, allPairs.length);
    }

नवीन पेअरची माहिती स्थिती व्हेरिएबल्समध्ये सेव्ह करा आणि जगाला नवीन पेअर एक्सचेंजची माहिती देण्यासाठी एक घटना उत्सर्जित करा.

ही दोन फंक्शन्स feeSetter ला फी प्राप्तकर्त्यावर (जर कोणी असेल तर) नियंत्रण ठेवण्याची आणि feeSetter ला नवीन पत्त्यावर बदलण्याची परवानगी देतात.

UniswapV2ERC20.sol

हे कॉन्ट्रॅक्ट (opens in a new tab) ERC-20 तरलता टोकन लागू करते. हे ओपनझेपलिन ERC-20 कॉन्ट्रॅक्टसारखे आहे, त्यामुळे मी फक्त जो भाग वेगळा आहे, permit कार्यक्षमता, तोच स्पष्ट करेन.

इथेरियमवरील व्यवहारांसाठी इथर (ETH) खर्च होतो, जे खऱ्या पैशाच्या समतुल्य आहे. जर तुमच्याकडे ERC-20 टोकन्स असतील परंतु ETH नसेल, तर तुम्ही व्यवहार पाठवू शकत नाही, त्यामुळे तुम्ही त्यांच्यासोबत काहीही करू शकत नाही. ही समस्या टाळण्यासाठी एक उपाय म्हणजे मेटा-व्यवहार (opens in a new tab). टोकन्सचा मालक एका व्यवहारावर स्वाक्षरी करतो जो दुसऱ्या कोणालातरी साखळीबाह्य टोकन्स काढण्याची परवानगी देतो आणि इंटरनेटचा वापर करून तो प्राप्तकर्त्याला पाठवतो. प्राप्तकर्ता, ज्याच्याकडे ETH आहे, तो नंतर मालकाच्या वतीने परवाना सबमिट करतो.

    bytes32 public DOMAIN_SEPARATOR;
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

हा हॅश व्यवहार प्रकारासाठी आयडेंटिफायर (opens in a new tab) आहे. आम्ही येथे समर्थन देत असलेला एकमेव प्रकार म्हणजे या पॅरामीटर्ससह Permit.

    mapping(address => uint) public nonces;

प्राप्तकर्त्यासाठी बनावट डिजिटल स्वाक्षरी करणे शक्य नाही. तथापि, तोच व्यवहार दोनदा पाठवणे क्षुल्लक आहे (हा रिप्ले ॲटॅकचा (opens in a new tab) एक प्रकार आहे). हे टाळण्यासाठी, आम्ही नॉन्स (opens in a new tab) वापरतो. जर नवीन Permit चा नॉन्स वापरलेल्या शेवटच्या नॉन्सपेक्षा एकने जास्त नसेल, तर आम्ही तो अवैध असल्याचे गृहीत धरतो.

    constructor() public {
        uint chainId;
        assembly {
            chainId := chainid
        }

चेन आयडेंटिफायर (opens in a new tab) पुनर्प्राप्त करण्यासाठी हा कोड आहे. हे Yul (opens in a new tab) नावाच्या EVM असेंब्ली बोलीचा वापर करते. लक्षात घ्या की Yul च्या सध्याच्या आवृत्तीमध्ये तुम्हाला chainid() वापरावे लागेल, chainid नाही.

EIP-712 साठी डोमेन सेपरेटरची (opens in a new tab) गणना करा.

    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {

हे फंक्शन आहे जे परवानग्या लागू करते. हे संबंधित फील्ड्स आणि स्वाक्षरीसाठी (opens in a new tab) तीन स्केलर मूल्ये (v, r, आणि s) पॅरामीटर्स म्हणून प्राप्त करते.

        require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');

अंतिम मुदतीनंतर व्यवहार स्वीकारू नका.

        bytes32 digest = keccak256(
            abi.encodePacked(
                '\x19\x01',
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            )
        );

abi.encodePacked(...) हा संदेश आम्हाला मिळण्याची अपेक्षा आहे. नॉन्स काय असावा हे आम्हाला माहित आहे, त्यामुळे आम्हाला तो पॅरामीटर म्हणून मिळवण्याची गरज नाही.

इथेरियम स्वाक्षरी अल्गोरिदमला स्वाक्षरी करण्यासाठी 256 बिट्स मिळण्याची अपेक्षा असते, त्यामुळे आम्ही keccak256 हॅश फंक्शन वापरतो.

        address recoveredAddress = ecrecover(digest, v, r, s);

डायजेस्ट आणि स्वाक्षरीवरून आम्ही ecrecover (opens in a new tab) वापरून स्वाक्षरी केलेल्याचा पत्ता मिळवू शकतो.

        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
        _approve(owner, spender, value);
    }

जर सर्व काही ठीक असेल, तर याला ERC-20 मंजूर करणे (opens in a new tab) म्हणून माना.

पेरिफेरी कॉन्ट्रॅक्ट्स

पेरिफेरी कॉन्ट्रॅक्ट्स हे युनिस्वॅपसाठी API (ॲप्लिकेशन प्रोग्राम इंटरफेस) आहेत. ते बाह्य कॉल्ससाठी उपलब्ध आहेत, मग ते इतर कॉन्ट्रॅक्ट्समधून असोत किंवा विकेंद्रित ॲप्लिकेशन्समधून. तुम्ही थेट कोर कॉन्ट्रॅक्ट्सना कॉल करू शकता, परंतु ते अधिक गुंतागुंतीचे आहे आणि जर तुम्ही चूक केली तर तुमचे मूल्य गमावले जाऊ शकते. कोर कॉन्ट्रॅक्ट्समध्ये केवळ त्यांची फसवणूक होणार नाही याची खात्री करण्यासाठी चाचण्या असतात, इतर कोणासाठीही सॅनिटी चेक्स नसतात. ते पेरिफेरीमध्ये असतात जेणेकरून आवश्यकतेनुसार ते अपडेट केले जाऊ शकतील.

UniswapV2Router01.sol

या कॉन्ट्रॅक्टमध्ये (opens in a new tab) समस्या आहेत, आणि यापुढे त्याचा वापर केला जाऊ नये (opens in a new tab). सुदैवाने, पेरिफेरी कॉन्ट्रॅक्ट्स स्टेटलेस असतात आणि त्यांच्याकडे कोणतीही मालमत्ता नसते, त्यामुळे ते नाकारणे आणि लोकांना त्याऐवजी UniswapV2Router02 हा पर्याय वापरण्याचा सल्ला देणे सोपे आहे.

UniswapV2Router02.sol

बहुतांश प्रकरणांमध्ये तुम्ही या कॉन्ट्रॅक्टद्वारे (opens in a new tab) युनिस्वॅप वापराल. तुम्ही ते कसे वापरायचे ते येथे (opens in a new tab) पाहू शकता.

यापैकी बहुतांश गोष्टी आपण यापूर्वी पाहिल्या आहेत किंवा त्या अगदी स्पष्ट आहेत. याला एक अपवाद म्हणजे IWETH.sol. युनिस्वॅप v2 कोणत्याही ERC-20 टोकन्सच्या जोडीसाठी अदलाबदल करण्यास अनुमती देते, परंतु इथर (ETH) स्वतः एक ERC-20 टोकन नाही. ते या मानकापूर्वीचे आहे आणि अद्वितीय यंत्रणेद्वारे त्याचे हस्तांतरण केले जाते. ERC-20 टोकन्सना लागू होणाऱ्या कॉन्ट्रॅक्ट्समध्ये ETH चा वापर सक्षम करण्यासाठी लोकांनी रॅप्ड इथर (weth) (opens in a new tab) कॉन्ट्रॅक्ट तयार केले. तुम्ही या कॉन्ट्रॅक्टला ETH पाठवता आणि ते तुम्हाला समतुल्य प्रमाणात WETH मिंट करून देते. किंवा तुम्ही WETH जाळू शकता आणि ETH परत मिळवू शकता.

contract UniswapV2Router02 is IUniswapV2Router02 {
    using SafeMath for uint;

    address public immutable override factory;
    address public immutable override WETH;

राउटरला कोणती फॅक्टरी वापरायची हे माहित असणे आवश्यक आहे, आणि ज्या व्यवहारांसाठी WETH आवश्यक आहे त्यांच्यासाठी कोणते WETH कॉन्ट्रॅक्ट वापरायचे हे माहित असणे आवश्यक आहे. ही मूल्ये अपरिवर्तनीय (opens in a new tab) आहेत, याचा अर्थ ती केवळ कन्स्ट्रक्टरमध्ये सेट केली जाऊ शकतात. यामुळे वापरकर्त्यांना असा विश्वास मिळतो की कोणीही त्यांना कमी प्रामाणिक कॉन्ट्रॅक्ट्सकडे निर्देशित करण्यासाठी बदलू शकणार नाही.

    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
        _;
    }

हा मॉडिफायर हे सुनिश्चित करतो की वेळेची मर्यादा असलेले व्यवहार ("शक्य असल्यास Y वेळेपूर्वी X करा") त्यांच्या वेळेच्या मर्यादेनंतर होणार नाहीत.

    constructor(address _factory, address _WETH) public {
        factory = _factory;
        WETH = _WETH;
    }

कन्स्ट्रक्टर फक्त अपरिवर्तनीय स्थिती व्हेरिएबल्स सेट करतो.

    receive() external payable {
        assert(msg.sender == WETH); // फक्त WETH कॉन्ट्रॅक्टमधून फॉलबॅकद्वारे ETH स्वीकारा
    }

जेव्हा आपण WETH कॉन्ट्रॅक्टमधून टोकन्स परत ETH मध्ये रिडीम करतो तेव्हा या फंक्शनला कॉल केला जातो. केवळ आपण वापरत असलेल्या WETH कॉन्ट्रॅक्टलाच तसे करण्याचा अधिकार आहे.

तरलता जोडणे

ही फंक्शन्स जोडीच्या अदलाबदलीमध्ये टोकन्स जोडतात, ज्यामुळे तरलता पूल वाढतो.


    // **** तरलता जोडा ****
    function _addLiquidity(

या फंक्शनचा वापर जोडीच्या अदलाबदलीमध्ये जमा केल्या जाणाऱ्या A आणि B टोकन्सच्या प्रमाणाची गणना करण्यासाठी केला जातो.

        address tokenA,
        address tokenB,

हे ERC-20 टोकन कॉन्ट्रॅक्ट्सचे पत्ते आहेत.

        uint amountADesired,
        uint amountBDesired,

ही ती रक्कम आहे जी तरलता प्रदाता जमा करू इच्छितो. ती जमा केली जाणारी A आणि B ची कमाल रक्कम देखील आहे.

        uint amountAMin,
        uint amountBMin

जमा करण्यासाठी ही किमान स्वीकार्य रक्कम आहे. जर या रकमेसह किंवा त्याहून अधिक रकमेसह व्यवहार होऊ शकत नसेल, तर तो पूर्ववत करा. जर तुम्हाला हे वैशिष्ट्य नको असेल, तर फक्त शून्य निर्दिष्ट करा.

तरलता प्रदाता सामान्यतः किमान रक्कम निर्दिष्ट करतात, कारण त्यांना व्यवहार सध्याच्या विनिमय दराच्या जवळ असलेल्या दरापर्यंत मर्यादित ठेवायचा असतो. जर विनिमय दरात खूप चढ-उतार होत असेल तर याचा अर्थ असा असू शकतो की मूळ मूल्ये बदलणाऱ्या बातम्या आहेत, आणि त्यांना काय करायचे ते मॅन्युअली ठरवायचे असते.

उदाहरणार्थ, अशा प्रकरणाची कल्पना करा जिथे विनिमय दर एकास एक आहे आणि तरलता प्रदाता ही मूल्ये निर्दिष्ट करतो:

पॅरामीटरमूल्य
amountADesired1000
amountBDesired1000
amountAMin900
amountBMin800

जोपर्यंत विनिमय दर 0.9 आणि 1.25 च्या दरम्यान राहतो, तोपर्यंत व्यवहार होतो. जर विनिमय दर त्या श्रेणीच्या बाहेर गेला, तर व्यवहार रद्द होतो.

या खबरदारीचे कारण असे आहे की व्यवहार त्वरित होत नाहीत, तुम्ही ते सबमिट करता आणि शेवटी एक प्रमाणक त्यांना ब्लॉकमध्ये समाविष्ट करणार असतो (जोपर्यंत तुमची गॅसची किंमत खूप कमी नसेल, अशा परिस्थितीत तुम्हाला तो ओव्हरराइट करण्यासाठी त्याच नॉन्ससह आणि उच्च गॅसच्या किंमतीसह दुसरा व्यवहार सबमिट करावा लागेल). सबमिशन आणि समावेशाच्या दरम्यानच्या काळात काय होते यावर तुमचे नियंत्रण नसते.

    ) internal virtual returns (uint amountA, uint amountB) {

हे फंक्शन ती रक्कम परत करते जी तरलता प्रदात्याने जमा केली पाहिजे जेणेकरून रिझर्व्हमधील सध्याच्या गुणोत्तराइतके गुणोत्तर मिळेल.

        // जर जोडी अद्याप अस्तित्वात नसेल तर ती तयार करा
        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }

जर या टोकन जोडीसाठी अद्याप कोणतीही अदलाबदल नसेल, तर ती तयार करा.

        (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);

जोडीमधील सध्याचे रिझर्व्ह मिळवा.

        if (reserveA == 0 && reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);

जर सध्याचे रिझर्व्ह रिक्त असतील तर ही एक नवीन जोडीची अदलाबदल आहे. जमा केली जाणारी रक्कम तरलता प्रदाता देऊ इच्छित असलेल्या रकमेइतकीच असली पाहिजे.

        } else {
            uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);

जर आपल्याला रक्कम किती असेल हे पाहण्याची आवश्यकता असेल, तर आपण या फंक्शनचा (opens in a new tab) वापर करून इष्टतम रक्कम मिळवतो. आपल्याला सध्याच्या रिझर्व्हसारखेच गुणोत्तर हवे आहे.

            if (amountBOptimal <= amountBDesired) {
                require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
                (amountA, amountB) = (amountADesired, amountBOptimal);

जर amountBOptimal तरलता प्रदाता जमा करू इच्छित असलेल्या रकमेपेक्षा लहान असेल, तर याचा अर्थ असा की टोकन B सध्या तरलता जमा करणाऱ्याच्या विचारापेक्षा अधिक मौल्यवान आहे, त्यामुळे कमी रक्कम आवश्यक आहे.

            } else {
                uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
                assert(amountAOptimal <= amountADesired);
                require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
                (amountA, amountB) = (amountAOptimal, amountBDesired);

जर इष्टतम B रक्कम इच्छित B रकमेपेक्षा जास्त असेल, तर याचा अर्थ असा की B टोकन्स सध्या तरलता जमा करणाऱ्याच्या विचारापेक्षा कमी मौल्यवान आहेत, त्यामुळे जास्त रक्कम आवश्यक आहे. तथापि, इच्छित रक्कम ही कमाल रक्कम आहे, त्यामुळे आपण तसे करू शकत नाही. त्याऐवजी आपण B टोकन्सच्या इच्छित रकमेसाठी A टोकन्सच्या इष्टतम संख्येची गणना करतो.

हे सर्व एकत्र केल्यावर आपल्याला हा आलेख मिळतो. समजा तुम्ही एक हजार A टोकन्स (निळी रेषा) आणि एक हजार B टोकन्स (लाल रेषा) जमा करण्याचा प्रयत्न करत आहात. x अक्ष हा विनिमय दर आहे, A/B. जर x=1 असेल, तर त्यांचे मूल्य समान आहे आणि तुम्ही प्रत्येकी एक हजार जमा करता. जर x=2 असेल, तर A चे मूल्य B च्या दुप्पट आहे (तुम्हाला प्रत्येक A टोकनसाठी दोन B टोकन्स मिळतात) त्यामुळे तुम्ही एक हजार B टोकन्स जमा करता, परंतु फक्त 500 A टोकन्स. जर x=0.5 असेल, तर परिस्थिती उलट असते, एक हजार A टोकन्स आणि पाचशे B टोकन्स.

Graph

तुम्ही थेट कोर कॉन्ट्रॅक्टमध्ये तरलता जमा करू शकता (UniswapV2Pair::mint (opens in a new tab) वापरून), परंतु कोर कॉन्ट्रॅक्ट केवळ स्वतःची फसवणूक होत नाही ना हे तपासते, त्यामुळे तुम्ही तुमचा व्यवहार सबमिट करता आणि तो कार्यान्वित होतो या दरम्यान विनिमय दर बदलल्यास तुमचे मूल्य गमावण्याचा धोका असतो. जर तुम्ही पेरिफेरी कॉन्ट्रॅक्ट वापरत असाल, तर ते तुम्ही किती रक्कम जमा करावी हे ठरवते आणि ती त्वरित जमा करते, त्यामुळे विनिमय दर बदलत नाही आणि तुमचे काहीही नुकसान होत नाही.

तरलता जमा करण्यासाठी व्यवहाराद्वारे या फंक्शनला कॉल केला जाऊ शकतो. बहुतांश पॅरामीटर्स वरील _addLiquidity प्रमाणेच आहेत, दोन अपवाद वगळता:

. to हा तो पत्ता आहे ज्याला पूलमधील तरलता प्रदात्याचा वाटा दर्शविण्यासाठी नवीन तरलता टोकन्स मिंट करून मिळतात . deadline ही व्यवहारावरील वेळेची मर्यादा आहे

    ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
        (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);

आपण प्रत्यक्षात जमा करावयाच्या रकमेची गणना करतो आणि नंतर तरलता पूलचा पत्ता शोधतो. गॅस वाचवण्यासाठी आपण हे फॅक्टरीला विचारून करत नाही, तर pairFor हे लायब्ररी फंक्शन वापरून करतो (खाली लायब्ररीमध्ये पहा)

        TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
        TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);

वापरकर्त्याकडून जोडीच्या अदलाबदलीमध्ये टोकन्सची योग्य रक्कम हस्तांतरित करा.

        liquidity = IUniswapV2Pair(pair).mint(to);
    }

त्या बदल्यात पूलच्या आंशिक मालकीसाठी to पत्त्याला तरलता टोकन्स द्या. कोर कॉन्ट्रॅक्टचे mint फंक्शन पाहते की त्याच्याकडे किती अतिरिक्त टोकन्स आहेत (गेल्या वेळी तरलता बदलली तेव्हा त्याच्याकडे जे होते त्याच्या तुलनेत) आणि त्यानुसार तरलता मिंट करते.

    function addLiquidityETH(
        address token,
        uint amountTokenDesired,

जेव्हा एखादा तरलता प्रदाता टोकन/ETH जोडीच्या अदलाबदलीसाठी तरलता प्रदान करू इच्छितो, तेव्हा काही फरक असतात. कॉन्ट्रॅक्ट तरलता प्रदात्यासाठी ETH रॅप करण्याचे काम हाताळते. वापरकर्त्याला किती ETH जमा करायचे आहेत हे निर्दिष्ट करण्याची आवश्यकता नाही, कारण वापरकर्ता ते फक्त व्यवहारासोबत पाठवतो (ही रक्कम msg.value मध्ये उपलब्ध असते).

ETH जमा करण्यासाठी कॉन्ट्रॅक्ट प्रथम त्याला WETH मध्ये रॅप करते आणि नंतर WETH जोडीमध्ये हस्तांतरित करते. लक्षात घ्या की हस्तांतरण assert मध्ये रॅप केलेले आहे. याचा अर्थ असा की जर हस्तांतरण अयशस्वी झाले तर हा कॉन्ट्रॅक्ट कॉल देखील अयशस्वी होतो, आणि त्यामुळे रॅपिंग खरोखर होत नाही.

        liquidity = IUniswapV2Pair(pair).mint(to);
        // डस्ट eth असल्यास, परतावा द्या
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
    }

वापरकर्त्याने आपल्याला आधीच ETH पाठवले आहेत, त्यामुळे जर काही अतिरिक्त उरले असेल (कारण दुसरे टोकन वापरकर्त्याच्या विचारापेक्षा कमी मौल्यवान आहे), तर आपल्याला परतावा जारी करणे आवश्यक आहे.

तरलता काढून घेणे

ही फंक्शन्स तरलता काढून घेतील आणि तरलता प्रदात्याला परतफेड करतील.

तरलता काढून घेण्याचे सर्वात सोपे प्रकरण. प्रत्येक टोकनची किमान रक्कम असते जी तरलता प्रदाता स्वीकारण्यास सहमत असतो, आणि ते अंतिम मुदतीपूर्वी होणे आवश्यक आहे.

        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // जोडीला तरलता पाठवा
        (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);

कोर कॉन्ट्रॅक्टचे burn फंक्शन वापरकर्त्याला टोकन्स परत देण्याचे काम हाताळते.

        (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);

जेव्हा एखादे फंक्शन अनेक मूल्ये परत करते, परंतु आपल्याला त्यापैकी काहींमध्येच स्वारस्य असते, तेव्हा आपण केवळ तीच मूल्ये अशा प्रकारे मिळवतो. मूल्य वाचून त्याचा कधीही वापर न करण्यापेक्षा गॅसच्या दृष्टीने हे काहीसे स्वस्त आहे.

        (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);

कोर कॉन्ट्रॅक्ट ज्या प्रकारे रक्कम परत करते (कमी पत्त्याचे टोकन प्रथम) त्यावरून वापरकर्त्याला ज्या प्रकारे अपेक्षित आहे ( tokenA आणि tokenB शी संबंधित) त्यामध्ये रकमेचे भाषांतर करा.

        require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
        require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
    }

प्रथम हस्तांतरण करणे आणि नंतर ते कायदेशीर आहे की नाही हे सत्यापित करणे ठीक आहे, कारण जर ते नसेल तर आपण सर्व स्थिती बदल पूर्ववत करू.

ETH साठी तरलता काढून घेणे जवळजवळ सारखेच आहे, फक्त एवढाच फरक आहे की आपल्याला WETH टोकन्स मिळतात आणि नंतर आपण तरलता प्रदात्याला परत देण्यासाठी त्यांना ETH साठी रिडीम करतो.

ही फंक्शन्स इथर नसलेल्या वापरकर्त्यांना परमिट यंत्रणेचा वापर करून पूलमधून रक्कम काढण्याची अनुमती देण्यासाठी मेटा-व्यवहार रिले करतात.

हे फंक्शन अशा टोकन्ससाठी वापरले जाऊ शकते ज्यांचे हस्तांतरण किंवा स्टोरेज शुल्क आहे. जेव्हा एखाद्या टोकनला असे शुल्क असते तेव्हा आपल्याला किती टोकन परत मिळतील हे सांगण्यासाठी आपण removeLiquidity फंक्शनवर अवलंबून राहू शकत नाही, त्यामुळे आपल्याला प्रथम रक्कम काढावी लागते आणि नंतर शिल्लक मिळवावी लागते.

अंतिम फंक्शन स्टोरेज शुल्काला मेटा-व्यवहारांसह एकत्र करते.

व्यापार

    // **** अदलाबदल ****
    // प्रारंभिक रक्कम पहिल्या जोडीला आधीच पाठवली असणे आवश्यक आहे
    function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {

हे फंक्शन अंतर्गत प्रक्रिया करते जी व्यापाऱ्यांसाठी उघड केलेल्या फंक्शन्ससाठी आवश्यक असते.

        for (uint i; i < path.length - 1; i++) {

मी हे लिहित असताना 388,160 ERC-20 टोकन्स (opens in a new tab) आहेत. जर प्रत्येक टोकन जोडीसाठी जोडीची अदलाबदल असती, तर 150 अब्जांहून अधिक जोडीच्या अदलाबदली झाल्या असत्या. सध्या संपूर्ण चेनवर त्या संख्येच्या केवळ 0.1% खाती आहेत (opens in a new tab). त्याऐवजी, स्वॅप फंक्शन्स मार्गाच्या संकल्पनेला समर्थन देतात. एक व्यापारी A ची B साठी, B ची C साठी आणि C ची D साठी अदलाबदल करू शकतो, त्यामुळे थेट A-D जोडीच्या अदलाबदलीची आवश्यकता नाही.

या बाजारांमधील किमती समक्रमित (synchronized) असतात, कारण जेव्हा त्या समक्रमित नसतात तेव्हा ते आर्बिट्रेजसाठी (arbitrage) संधी निर्माण करते. उदाहरणार्थ, A, B आणि C या तीन टोकन्सची कल्पना करा. प्रत्येक जोडीसाठी एक अशा तीन जोडीच्या अदलाबदली आहेत.

  1. सुरुवातीची परिस्थिती
  2. एक व्यापारी 24.695 A टोकन्स विकतो आणि 25.305 B टोकन्स मिळवतो.
  3. व्यापारी 24.695 B टोकन्स 25.305 C टोकन्ससाठी विकतो, आणि अंदाजे 0.61 B टोकन्स नफा म्हणून ठेवतो.
  4. त्यानंतर व्यापारी 24.695 C टोकन्स 25.305 A टोकन्ससाठी विकतो, आणि अंदाजे 0.61 C टोकन्स नफा म्हणून ठेवतो. व्यापाऱ्याकडे 0.61 अतिरिक्त A टोकन्स देखील असतात (व्यापाऱ्याकडे शेवटी उरलेले 25.305, वजा 24.695 ची मूळ गुंतवणूक).
पायरीA-B अदलाबदलB-C अदलाबदलA-C अदलाबदल
1A:1000 B:1050 A/B=1.05B:1000 C:1050 B/C=1.05A:1050 C:1000 C/A=1.05
2A:1024.695 B:1024.695 A/B=1B:1000 C:1050 B/C=1.05A:1050 C:1000 C/A=1.05
3A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1050 C:1000 C/A=1.05
4A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1024.695 C:1024.695 C/A=1
            (address input, address output) = (path[i], path[i + 1]);
            (address token0,) = UniswapV2Library.sortTokens(input, output);
            uint amountOut = amounts[i + 1];

आपण सध्या हाताळत असलेली जोडी मिळवा, तिची क्रमवारी लावा (जोडीसोबत वापरण्यासाठी) आणि अपेक्षित आउटपुट रक्कम मिळवा.

            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));

जोडीच्या अदलाबदलीला ज्या प्रकारे अपेक्षित आहे त्या प्रकारे क्रमवारी लावलेली अपेक्षित आउटपुट रक्कम मिळवा.

            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;

ही शेवटची अदलाबदल आहे का? तसे असल्यास, व्यापारासाठी मिळालेले टोकन्स गंतव्यस्थानावर पाठवा. नसल्यास, ते पुढील जोडीच्या अदलाबदलीकडे पाठवा.


            IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                amount0Out, amount1Out, to, new bytes(0)
            );
        }
    }

टोकन्सची अदलाबदल करण्यासाठी प्रत्यक्षात जोडीच्या अदलाबदलीला कॉल करा. अदलाबदलीबद्दल सांगण्यासाठी आपल्याला कॉलबॅकची आवश्यकता नाही, त्यामुळे आपण त्या फील्डमध्ये कोणतेही बाइट्स पाठवत नाही.

    function swapExactTokensForTokens(

एका टोकनची दुसऱ्या टोकनशी अदलाबदल करण्यासाठी व्यापाऱ्यांद्वारे या फंक्शनचा थेट वापर केला जातो.

        uint amountIn,
        uint amountOutMin,
        address[] calldata path,

या पॅरामीटरमध्ये ERC-20 कॉन्ट्रॅक्ट्सचे पत्ते असतात. वर स्पष्ट केल्याप्रमाणे, हा एक ॲरे आहे कारण तुमच्याकडे असलेल्या मालमत्तेवरून तुम्हाला हव्या असलेल्या मालमत्तेपर्यंत पोहोचण्यासाठी तुम्हाला अनेक जोडीच्या अदलाबदलींमधून जावे लागू शकते.

Solidity मधील फंक्शन पॅरामीटर एकतर memory मध्ये किंवा calldata मध्ये साठवले जाऊ शकते. जर फंक्शन हे कॉन्ट्रॅक्टचा एंट्री पॉईंट असेल, ज्याला वापरकर्त्याकडून (व्यवहार वापरून) किंवा वेगळ्या कॉन्ट्रॅक्टमधून थेट कॉल केला असेल, तर पॅरामीटरचे मूल्य थेट कॉल डेटामधून घेतले जाऊ शकते. जर फंक्शनला अंतर्गत कॉल केला असेल, जसे की वरील _swap, तर पॅरामीटर्स memory मध्ये साठवावे लागतात. कॉल केलेल्या कॉन्ट्रॅक्टच्या दृष्टिकोनातून calldata हे केवळ वाचनीय (read only) असते.

uint किंवा address सारख्या स्केलर प्रकारांसह कंपायलर आपल्यासाठी स्टोरेजची निवड हाताळतो, परंतु ॲरेसह, जे मोठे आणि अधिक महाग असतात, आपण वापरल्या जाणाऱ्या स्टोरेजचा प्रकार निर्दिष्ट करतो.

        address to,
        uint deadline
    ) external virtual override ensure(deadline) returns (uint[] memory amounts) {

रिटर्न व्हॅल्यूज नेहमी मेमरीमध्ये परत केल्या जातात.

        amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
        require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');

प्रत्येक अदलाबदलीमध्ये खरेदी करावयाच्या रकमेची गणना करा. जर परिणाम व्यापारी स्वीकारण्यास तयार असलेल्या किमान रकमेपेक्षा कमी असेल, तर व्यवहार पूर्ववत करा.

        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );
        _swap(amounts, path, to);
    }

शेवटी, पहिल्या जोडीच्या अदलाबदलीसाठी प्रारंभिक ERC-20 टोकन खात्यात हस्तांतरित करा आणि _swap ला कॉल करा. हे सर्व एकाच व्यवहारात घडत आहे, त्यामुळे जोडीच्या अदलाबदलीला माहित असते की कोणतेही अनपेक्षित टोकन्स या हस्तांतरणाचा भाग आहेत.

मागील फंक्शन, swapTokensForTokens, व्यापाऱ्याला तो देऊ इच्छित असलेल्या इनपुट टोकन्सची अचूक संख्या आणि त्या बदल्यात तो प्राप्त करण्यास तयार असलेल्या आउटपुट टोकन्सची किमान संख्या निर्दिष्ट करण्याची अनुमती देते. हे फंक्शन उलट अदलाबदल करते, ते व्यापाऱ्याला त्याला हव्या असलेल्या आउटपुट टोकन्सची संख्या आणि त्यासाठी तो देऊ इच्छित असलेल्या इनपुट टोकन्सची कमाल संख्या निर्दिष्ट करू देते.

दोन्ही प्रकरणांमध्ये, व्यापाऱ्याला प्रथम या पेरिफेरी कॉन्ट्रॅक्टला ते हस्तांतरित करण्याची अनुमती देण्यासाठी मंजुरी द्यावी लागते.

या चारही प्रकारांमध्ये ETH आणि टोकन्स यांच्यातील व्यापाराचा समावेश आहे. फरक एवढाच आहे की आपण एकतर व्यापाऱ्याकडून ETH प्राप्त करतो आणि WETH मिंट करण्यासाठी त्याचा वापर करतो, किंवा आपण मार्गातील शेवटच्या अदलाबदलीकडून WETH प्राप्त करतो आणि ते जाळतो, आणि परिणामी ETH व्यापाऱ्याला परत पाठवतो.

    // **** अदलाबदल (फी-ऑन-हस्तांतरण टोकन्सना समर्थन देत आहे) ****
    // प्रारंभिक रक्कम पहिल्या जोडीला आधीच पाठवली असणे आवश्यक आहे
    function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {

(ही समस्या (opens in a new tab)) सोडवण्यासाठी हस्तांतरण किंवा स्टोरेज शुल्क असलेल्या टोकन्सची अदलाबदल करण्यासाठी हे अंतर्गत फंक्शन आहे.

हस्तांतरण शुल्कामुळे आपण प्रत्येक हस्तांतरणातून आपल्याला किती मिळते हे सांगण्यासाठी getAmountsOut फंक्शनवर अवलंबून राहू शकत नाही (जसे आपण मूळ _swap ला कॉल करण्यापूर्वी करतो). त्याऐवजी आपल्याला प्रथम हस्तांतरण करावे लागते आणि नंतर आपल्याला किती टोकन्स परत मिळाले ते पहावे लागते.

टीप: सैद्धांतिकदृष्ट्या आपण _swap ऐवजी फक्त हे फंक्शन वापरू शकतो, परंतु काही प्रकरणांमध्ये (उदाहरणार्थ, जर आवश्यक किमान रक्कम पूर्ण करण्यासाठी शेवटी पुरेशी रक्कम नसल्यामुळे हस्तांतरण पूर्ववत झाले) तर त्यासाठी अधिक गॅस खर्च होईल. ट्रान्सफर फी टोकन्स खूप दुर्मिळ आहेत, त्यामुळे आपल्याला त्यांना सामावून घेण्याची आवश्यकता असली तरी, सर्व अदलाबदलींनी ते किमान एका ट्रान्सफर फी टोकनमधून जातील असे गृहीत धरण्याची आवश्यकता नाही.

हे सामान्य टोकन्ससाठी वापरले जाणारे तेच प्रकार आहेत, परंतु ते त्याऐवजी _swapSupportingFeeOnTransferTokens ला कॉल करतात.

ही फंक्शन्स फक्त प्रॉक्सी आहेत जी UniswapV2Library फंक्शन्सना कॉल करतात.

UniswapV2Migrator.sol

या कॉन्ट्रॅक्टचा वापर जुन्या v1 वरून v2 मध्ये अदलाबदली स्थलांतरित करण्यासाठी केला गेला होता. आता ते स्थलांतरित झाले असल्याने, ते यापुढे संबंधित नाही.

लायब्ररी

SafeMath लायब्ररी (opens in a new tab) चांगल्या प्रकारे दस्तऐवजीकरण केलेली आहे, त्यामुळे येथे तिचे दस्तऐवजीकरण करण्याची आवश्यकता नाही.

Math

या लायब्ररीमध्ये काही गणितीय फंक्शन्स आहेत ज्यांची सामान्यतः Solidity कोडमध्ये आवश्यकता नसते, त्यामुळे ते या भाषेचा भाग नाहीत.

x ने असा अंदाज म्हणून सुरुवात करा जो वर्गमुळापेक्षा जास्त असेल (याच कारणामुळे आपल्याला 1-3 ला विशेष प्रकरणे म्हणून हाताळण्याची आवश्यकता आहे).

            while (x < z) {
                z = x;
                x = (y / x + x) / 2;

अधिक जवळचा अंदाज मिळवा, मागील अंदाज आणि ज्या संख्येचे वर्गमूळ आपण शोधण्याचा प्रयत्न करत आहोत त्या संख्येला मागील अंदाजाने भागून मिळणारी सरासरी. जोपर्यंत नवीन अंदाज विद्यमान अंदाजापेक्षा कमी होत नाही तोपर्यंत पुनरावृत्ती करा. अधिक तपशीलांसाठी, येथे पहा (opens in a new tab).

            }
        } else if (y != 0) {
            z = 1;

आपल्याला शून्याच्या वर्गमुळाची कधीही आवश्यकता भासू नये. एक, दोन आणि तीनचे वर्गमूळ साधारणपणे एक असते (आपण पूर्णांक वापरतो, त्यामुळे आपण अपूर्णांकाकडे दुर्लक्ष करतो).

        }
    }
}

फिक्स्ड पॉइंट फ्रॅक्शन्स (UQ112x112)

ही लायब्ररी अपूर्णांक हाताळते, जे सामान्यतः इथेरियम अंकगणिताचा भाग नसतात. ती x या संख्येला x*2^112 म्हणून एन्कोड करून हे करते. यामुळे आपल्याला मूळ बेरीज आणि वजाबाकी ऑपकोड्स कोणत्याही बदलाशिवाय वापरता येतात.

Q112 हे एकासाठी एन्कोडिंग आहे.

    // uint112 ला UQ112x112 म्हणून एन्कोड करा
    function encode(uint112 y) internal pure returns (uint224 z) {
        z = uint224(y) * Q112; // कधीही ओव्हरफ्लो होत नाही
    }

कारण y हे uint112 आहे, ते जास्तीत जास्त 2^112-1 असू शकते. ती संख्या अजूनही UQ112x112 म्हणून एन्कोड केली जाऊ शकते.

    // UQ112x112 ला uint112 ने भागा, UQ112x112 परत करते
    function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
        z = x / uint224(y);
    }
}

जर आपण दोन UQ112x112 मूल्यांना भागले, तर परिणामाला यापुढे 2^112 ने गुणले जात नाही. त्यामुळे त्याऐवजी आपण छेदासाठी एक पूर्णांक घेतो. गुणाकार करण्यासाठी आपल्याला अशीच युक्ती वापरावी लागली असती, परंतु आपल्याला UQ112x112 मूल्यांचा गुणाकार करण्याची आवश्यकता नाही.

UniswapV2Library

ही लायब्ररी केवळ पेरिफेरी (periphery) कॉन्ट्रॅक्ट्सद्वारे वापरली जाते

दोन टोकन्स त्यांच्या पत्त्यानुसार क्रमवारी लावा, जेणेकरून आपल्याला त्यांच्यासाठी पेअर एक्सचेंजचा पत्ता मिळवता येईल. हे आवश्यक आहे कारण अन्यथा आपल्याकडे दोन शक्यता असतील, एक A,B पॅरामीटर्ससाठी आणि दुसरी B,A पॅरामीटर्ससाठी, ज्यामुळे एकाऐवजी दोन एक्सचेंजेस तयार होतील.

हे फंक्शन दोन टोकन्ससाठी पेअर एक्सचेंजच्या पत्त्याची गणना करते. हे कॉन्ट्रॅक्ट CREATE2 ऑपकोड (opens in a new tab) वापरून तयार केले आहे, त्यामुळे जर आपल्याला ते वापरत असलेले पॅरामीटर्स माहित असतील तर आपण त्याच अल्गोरिदमचा वापर करून पत्त्याची गणना करू शकतो. फॅक्टरीला विचारण्यापेक्षा हे खूप स्वस्त आहे, आणि

    // जोडीसाठी रिझर्व्ह मिळवते आणि क्रमवारी लावते
    function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
        (address token0,) = sortTokens(tokenA, tokenB);
        (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
        (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
    }

हे फंक्शन पेअर एक्सचेंजकडे असलेल्या दोन टोकन्सचे रिझर्व्ह परत करते. लक्षात घ्या की ते कोणत्याही क्रमाने टोकन्स प्राप्त करू शकते आणि अंतर्गत वापरासाठी त्यांची क्रमवारी लावते.

    // मालमत्तेची काही रक्कम आणि जोडीचे रिझर्व्ह दिले असता, दुसऱ्या मालमत्तेची समतुल्य रक्कम परत करते
    function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
        require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
        require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        amountB = amountA.mul(reserveB) / reserveA;
    }

जर कोणतेही शुल्क समाविष्ट नसेल तर हे फंक्शन तुम्हाला टोकन A च्या बदल्यात मिळणाऱ्या टोकन B ची रक्कम देते. ही गणना हे लक्षात घेते की हस्तांतरणामुळे विनिमय दर बदलतो.

    // मालमत्तेची इनपुट रक्कम आणि जोडीचे रिझर्व्ह दिले असता, दुसऱ्या मालमत्तेची कमाल आउटपुट रक्कम परत करते
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {

जर पेअर एक्सचेंज वापरण्यासाठी कोणतेही शुल्क नसेल तर वरील quote फंक्शन उत्तम काम करते. तथापि, जर 0.3% एक्सचेंज शुल्क असेल तर तुम्हाला प्रत्यक्षात मिळणारी रक्कम कमी असते. हे फंक्शन एक्सचेंज शुल्कानंतरच्या रकमेची गणना करते.


        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint amountInWithFee = amountIn.mul(997);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }

Solidity मूळतः अपूर्णांक हाताळत नाही, त्यामुळे आपण रकमेला फक्त 0.997 ने गुणू शकत नाही. त्याऐवजी, आपण अंशाला 997 ने आणि छेदाला 1000 ने गुणतो, ज्यामुळे तोच परिणाम साध्य होतो.

    // मालमत्तेची आउटपुट रक्कम आणि जोडीचे रिझर्व्ह दिले असता, दुसऱ्या मालमत्तेची आवश्यक इनपुट रक्कम परत करते
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
        require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint numerator = reserveIn.mul(amountOut).mul(1000);
        uint denominator = reserveOut.sub(amountOut).mul(997);
        amountIn = (numerator / denominator).add(1);
    }

हे फंक्शन साधारणपणे तेच काम करते, परंतु ते आउटपुट रक्कम मिळवते आणि इनपुट प्रदान करते.

जेव्हा अनेक पेअर एक्सचेंजेसमधून जाणे आवश्यक असते तेव्हा ही दोन फंक्शन्स मूल्ये ओळखण्याचे काम हाताळतात.

ट्रान्सफर हेल्पर

ही लायब्ररी (opens in a new tab) ERC-20 आणि इथेरियम हस्तांतरणाभोवती यश तपासणी जोडते जेणेकरून पूर्ववत करणे आणि false मूल्य परत येणे या दोन्ही गोष्टींना एकाच प्रकारे हाताळता येईल.

आपण दोनपैकी एका मार्गाने वेगळ्या कॉन्ट्रॅक्टला कॉल करू शकतो:

        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

ERC-20 मानकापूर्वी तयार केलेल्या टोकनसह बॅकवर्ड कंपॅटिबिलिटीसाठी, ERC-20 कॉल एकतर पूर्ववत करून (ज्या प्रकरणात success हे false असते) किंवा यशस्वी होऊन आणि false मूल्य परत करून (ज्या प्रकरणात आउटपुट डेटा असतो, आणि जर तुम्ही त्याला बुलियन म्हणून डीकोड केले तर तुम्हाला false मिळते) अयशस्वी होऊ शकतो.

हे फंक्शन ERC-20 ची हस्तांतरण कार्यक्षमता (opens in a new tab) लागू करते, जे एका खात्याला वेगळ्या खात्याने प्रदान केलेली मंजुरी खर्च करण्याची परवानगी देते.

हे फंक्शन ERC-20 ची transferFrom कार्यक्षमता (opens in a new tab) लागू करते, जे एका खात्याला वेगळ्या खात्याने प्रदान केलेली मंजुरी खर्च करण्याची परवानगी देते.


    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}

हे फंक्शन खात्यात इथर हस्तांतरित करते. वेगळ्या कॉन्ट्रॅक्टला केलेला कोणताही कॉल इथर पाठवण्याचा प्रयत्न करू शकतो. कारण आपल्याला प्रत्यक्षात कोणत्याही फंक्शनला कॉल करण्याची आवश्यकता नाही, आपण कॉलसोबत कोणताही डेटा पाठवत नाही.

निष्कर्ष

हा सुमारे 50 पानांचा एक मोठा लेख आहे. जर तुम्ही इथपर्यंत पोहोचला असाल, तर तुमचे अभिनंदन! आशा आहे की आतापर्यंत तुम्हाला वास्तविक जीवनातील अॅप्लिकेशन (लहान नमुना प्रोग्राम्सच्या तुलनेत) लिहिताना कोणत्या गोष्टींचा विचार करावा लागतो हे समजले असेल आणि तुम्ही तुमच्या स्वतःच्या वापरासाठी कॉन्ट्रॅक्ट्स अधिक चांगल्या प्रकारे लिहू शकाल.

आता जा आणि काहीतरी उपयुक्त लिहा आणि आम्हाला थक्क करा.

माझ्या अधिक कामासाठी येथे पहा (opens in a new tab).

पृष्ठ शेवटचे अपडेट: 3 एप्रिल, 2026