Uniswap-v2 कॉन्ट्रॅक्ट वॉक-थ्रू
परिचय
Uniswap v2 (opens in a new tab) कोणत्याही दोन ERC-20 टोकन्स दरम्यान एक्सचेंज मार्केट तयार करू शकते. या लेखामध्ये आपण या प्रोटोकॉलची अंमलबजावणी करणाऱ्या कॉन्ट्रॅक्ट्सच्या सोर्स कोडवर नजर टाकू आणि ते अशा प्रकारे का लिहिले आहेत ते पाहू.
Uniswap काय करते?
मूलतः, वापरकर्त्यांचे दोन प्रकार आहेत: लिक्विडिटी प्रोव्हायडर्स (तरलता पुरवठादार) आणि ट्रेडर्स (व्यापारी).
लिक्विडिटी प्रोव्हायडर्स पूलला दोन टोकन्स पुरवतात ज्यांची देवाणघेवाण केली जाऊ शकते (आपण त्यांना Token0 आणि Token1 म्हणू). या बदल्यात, त्यांना एक तिसरे टोकन मिळते जे पूलच्या आंशिक मालकीचे प्रतिनिधित्व करते, ज्याला लिक्विडिटी टोकन म्हणतात.
ट्रेडर्स पूलमध्ये एका प्रकारचे टोकन पाठवतात आणि लिक्विडिटी प्रोव्हायडर्सनी पुरवलेल्या पूलमधून दुसरे टोकन प्राप्त करतात (उदाहरणार्थ, Token0 पाठवा आणि Token1 प्राप्त करा). विनिमय दर (एक्सचेंज रेट) पूलकडे असलेल्या Token0 आणि Token1 च्या सापेक्ष संख्येनुसार ठरवला जातो. याव्यतिरिक्त, पूल लिक्विडिटी पूलसाठी बक्षीस म्हणून काही टक्केवारी घेतो.
जेव्हा लिक्विडिटी प्रोव्हायडर्सना त्यांची मालमत्ता परत हवी असते, तेव्हा ते पूल टोकन्स बर्न करू शकतात आणि बक्षिसांमधील त्यांच्या वाट्यासह त्यांचे टोकन्स परत मिळवू शकतात.
अधिक सविस्तर माहितीसाठी येथे क्लिक करा (opens in a new tab).
v2 का? v3 का नाही?
Uniswap v3 (opens in a new tab) हे एक अपग्रेड आहे जे v2 पेक्षा खूपच गुंतागुंतीचे आहे. आधी v2 शिकणे आणि नंतर v3 कडे जाणे सोपे आहे.
कोअर कॉन्ट्रॅक्ट्स विरूद्ध पेरिफेरी कॉन्ट्रॅक्ट्स
Uniswap v2 दोन घटकांमध्ये विभागलेले आहे, एक कोअर (मुख्य) आणि एक पेरिफेरी (बाह्य). या विभागणीमुळे कोअर कॉन्ट्रॅक्ट्स, ज्यांच्याकडे मालमत्ता असते आणि त्यामुळे ते सुरक्षित असायलाच हवेत, अधिक सोपे आणि ऑडिट करण्यासाठी सोयीस्कर बनतात. ट्रेडर्सना आवश्यक असलेली सर्व अतिरिक्त कार्यक्षमता नंतर पेरिफेरी कॉन्ट्रॅक्ट्सद्वारे प्रदान केली जाऊ शकते.
डेटा आणि कंट्रोल फ्लो
जेव्हा तुम्ही Uniswap च्या तीन मुख्य क्रिया करता तेव्हा डेटा आणि कंट्रोलचा फ्लो असा असतो:
- वेगवेगळ्या टोकन्समध्ये स्वॅप करणे
- मार्केटमध्ये लिक्विडिटी जोडणे आणि पेअर एक्सचेंज ERC-20 लिक्विडिटी टोकन्सच्या स्वरूपात रिवॉर्ड मिळवणे
- ERC-20 लिक्विडिटी टोकन्स बर्न करणे आणि पेअर एक्सचेंज ट्रेडर्सना एक्सचेंज करण्याची परवानगी देणारे ERC-20 टोकन्स परत मिळवणे
स्वॅप
हा सर्वात सामान्य फ्लो आहे, जो ट्रेडर्सद्वारे वापरला जातो:
कॉलर
- स्वॅप करायच्या रकमेसाठी पेरिफेरी अकाउंटला अलाउन्स (allowance) प्रदान करा.
- पेरिफेरी कॉन्ट्रॅक्टच्या अनेक स्वॅप फंक्शन्सपैकी एकाला कॉल करा (कोणते फंक्शन कॉल करायचे हे ETH समाविष्ट आहे की नाही, ट्रेडरने जमा करायच्या टोकन्सची रक्कम किंवा परत मिळवायच्या टोकन्सची रक्कम निर्दिष्ट केली आहे की नाही, इत्यादींवर अवलंबून असते).
प्रत्येक स्वॅप फंक्शन
pathस्वीकारते, जो पार करायच्या एक्सचेंजेसचा एक अॅरे (array) असतो.
पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)
- पाथवरील प्रत्येक एक्सचेंजवर ट्रेड केल्या जाणाऱ्या रकमा ओळखा.
- पाथवर इटरेट (Iterate) करते. वाटेतील प्रत्येक एक्सचेंजसाठी ते इनपुट टोकन पाठवते आणि नंतर एक्सचेंजच्या
swapफंक्शनला कॉल करते. बहुतेक प्रकरणांमध्ये टोकन्सचा डेस्टिनेशन अॅड्रेस हा पाथवरील पुढील पेअर एक्सचेंज असतो. अंतिम एक्सचेंजमध्ये तो ट्रेडरने दिलेला अॅड्रेस असतो.
कोअर कॉन्ट्रॅक्टमध्ये (UniswapV2Pair.sol)
- कोअर कॉन्ट्रॅक्टची फसवणूक होत नाहीये आणि स्वॅपनंतर पुरेशी लिक्विडिटी राखली जाऊ शकते याची पडताळणी करा.
- ज्ञात रिझर्व्ह व्यतिरिक्त आपल्याकडे किती अतिरिक्त टोकन्स आहेत ते पहा. ती रक्कम म्हणजे एक्सचेंज करण्यासाठी आपल्याला मिळालेल्या इनपुट टोकन्सची संख्या आहे.
- आउटपुट टोकन्स डेस्टिनेशनवर पाठवा.
- रिझर्व्ह रकमा अपडेट करण्यासाठी
_updateला कॉल करा
परत पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)
- कोणतीही आवश्यक साफसफाई करा (उदाहरणार्थ, ट्रेडरला पाठवण्यासाठी ETH परत मिळवण्यासाठी WETH टोकन्स बर्न करा)
लिक्विडिटी जोडणे
कॉलर
- लिक्विडिटी पूलमध्ये जोडल्या जाणाऱ्या रकमांसाठी पेरिफेरी अकाउंटला अलाउन्स प्रदान करा.
- पेरिफेरी कॉन्ट्रॅक्टच्या
addLiquidityफंक्शन्सपैकी एकाला कॉल करा.
पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)
- आवश्यक असल्यास नवीन पेअर एक्सचेंज तयार करा
- जर आधीपासूनच पेअर एक्सचेंज अस्तित्वात असेल, तर जोडायच्या टोकन्सच्या रकमेची गणना करा. हे दोन्ही टोकन्ससाठी समान मूल्य असणे अपेक्षित आहे, त्यामुळे नवीन टोकन्सचे विद्यमान टोकन्सशी समान गुणोत्तर असावे.
- रकमा स्वीकार्य आहेत का ते तपासा (कॉलर्स किमान रक्कम निर्दिष्ट करू शकतात ज्याच्या खाली ते लिक्विडिटी जोडणे पसंत करणार नाहीत)
- कोअर कॉन्ट्रॅक्टला कॉल करा.
कोअर कॉन्ट्रॅक्टमध्ये (UniswapV2Pair.sol)
- लिक्विडिटी टोकन्स मिंट करा आणि ते कॉलरला पाठवा
- रिझर्व्ह रकमा अपडेट करण्यासाठी
_updateला कॉल करा
लिक्विडिटी काढून टाकणे
कॉलर
- अंडरलाइंग टोकन्सच्या बदल्यात बर्न करण्यासाठी पेरिफेरी अकाउंटला लिक्विडिटी टोकन्सचा अलाउन्स प्रदान करा.
- पेरिफेरी कॉन्ट्रॅक्टच्या
removeLiquidityफंक्शन्सपैकी एकाला कॉल करा.
पेरिफेरी कॉन्ट्रॅक्टमध्ये (UniswapV2Router02.sol)
- पेअर एक्सचेंजला लिक्विडिटी टोकन्स पाठवा
कोअर कॉन्ट्रॅक्टमध्ये (UniswapV2Pair.sol)
- बर्न केलेल्या टोकन्सच्या प्रमाणात डेस्टिनेशन अॅड्रेसवर अंडरलाइंग टोकन्स पाठवा. उदाहरणार्थ, जर पूलमध्ये 1000 A टोकन्स, 500 B टोकन्स आणि 90 लिक्विडिटी टोकन्स असतील आणि आपल्याला बर्न करण्यासाठी 9 टोकन्स मिळाले, तर आपण 10% लिक्विडिटी टोकन्स बर्न करत आहोत, त्यामुळे आपण वापरकर्त्याला 100 A टोकन्स आणि 50 B टोकन्स परत पाठवतो.
- लिक्विडिटी टोकन्स बर्न करा
- रिझर्व्ह रकमा अपडेट करण्यासाठी
_updateला कॉल करा
मुख्य कॉन्ट्रॅक्ट्स
हे सुरक्षित कॉन्ट्रॅक्ट्स आहेत जे लिक्विडिटी (तरलता) धारण करतात.
UniswapV2Pair.sol
हे कॉन्ट्रॅक्ट (opens in a new tab) प्रत्यक्ष पूल लागू करते जे टोकन्सची देवाणघेवाण करते. ही मुख्य Uniswap कार्यक्षमता आहे.
1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Pair.sol';4import './UniswapV2ERC20.sol';5import './libraries/Math.sol';6import './libraries/UQ112x112.sol';7import './interfaces/IERC20.sol';8import './interfaces/IUniswapV2Factory.sol';9import './interfaces/IUniswapV2Callee.sol';सर्व दाखवाहे सर्व इंटरफेसेस आहेत ज्यांच्याबद्दल कॉन्ट्रॅक्टला माहिती असणे आवश्यक आहे, एकतर कारण कॉन्ट्रॅक्ट त्यांची अंमलबजावणी करते (IUniswapV2Pair आणि UniswapV2ERC20) किंवा कारण ते त्यांची अंमलबजावणी करणाऱ्या कॉन्ट्रॅक्ट्सना कॉल करते.
1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {हे कॉन्ट्रॅक्ट UniswapV2ERC20 कडून इनहेरिट करते, जे लिक्विडिटी टोकन्ससाठी ERC-20 फंक्शन्स प्रदान करते.
1 using SafeMath for uint;ओव्हरफ्लो आणि अंडरफ्लो टाळण्यासाठी SafeMath लायब्ररी (opens in a new tab) वापरली जाते. हे महत्त्वाचे आहे कारण अन्यथा आपण अशा परिस्थितीत अडकू शकतो जिथे मूल्य -1 असावे, परंतु त्याऐवजी ते 2^256-1 असते.
1 using UQ112x112 for uint224;पूल कॉन्ट्रॅक्टमधील अनेक गणनेसाठी अपूर्णांकांची आवश्यकता असते. तथापि, EVM द्वारे अपूर्णांकांना सपोर्ट नाही.
Uniswap ने शोधलेला उपाय म्हणजे 224 बिट व्हॅल्यूज वापरणे, ज्यामध्ये पूर्णांक भागासाठी 112 बिट्स आणि अपूर्णांकासाठी 112 बिट्स असतात. त्यामुळे 1.0 हे 2^112 म्हणून दर्शविले जाते, 1.5 हे 2^112 + 2^111 म्हणून दर्शविले जाते, इ.
या लायब्ररीबद्दल अधिक तपशील डॉक्युमेंटमध्ये पुढे उपलब्ध आहेत.
व्हेरिएबल्स
1 uint public constant MINIMUM_LIQUIDITY = 10**3;शून्याने भाग जाण्याची प्रकरणे टाळण्यासाठी, लिक्विडिटी टोकन्सची किमान संख्या असते जी नेहमी अस्तित्वात असते (परंतु ती अकाउंट झिरोच्या मालकीची असते). ती संख्या MINIMUM_LIQUIDITY आहे, एक हजार.
1 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));हा ERC-20 ट्रान्सफर फंक्शनसाठी ABI सिलेक्टर आहे. याचा वापर दोन टोकन अकाउंट्समध्ये ERC-20 टोकन्स ट्रान्सफर करण्यासाठी केला जातो.
1 address public factory;हे फॅक्टरी कॉन्ट्रॅक्ट आहे ज्याने हा पूल तयार केला आहे. प्रत्येक पूल हा दोन ERC-20 टोकन्समधील एक्सचेंज आहे, फॅक्टरी हा एक मध्यवर्ती बिंदू आहे जो या सर्व पूल्सना जोडतो.
1 address public token0;2 address public token1;या पूलद्वारे एक्सचेंज केल्या जाऊ शकणाऱ्या दोन प्रकारच्या ERC-20 टोकन्ससाठी कॉन्ट्रॅक्ट्सचे पत्ते येथे आहेत.
1 uint112 private reserve0; // एकाच स्टोरेज स्लॉटचा वापर करते, getReserves द्वारे प्रवेश करण्यायोग्य2 uint112 private reserve1; // एकाच स्टोरेज स्लॉटचा वापर करते, getReserves द्वारे प्रवेश करण्यायोग्यप्रत्येक टोकन प्रकारासाठी पूलकडे असलेले रिझर्व्ह्स. आम्ही असे गृहीत धरतो की दोन्ही समान मूल्याचे प्रतिनिधित्व करतात आणि म्हणून प्रत्येक token0 ची किंमत reserve1/reserve0 token1 इतकी असते.
1 uint32 private blockTimestampLast; // एकाच स्टोरेज स्लॉटचा वापर करते, getReserves द्वारे प्रवेश करण्यायोग्यज्या शेवटच्या ब्लॉकमध्ये एक्सचेंज झाले त्याचा टाइमस्टॅम्प, जो वेळेनुसार एक्सचेंज रेट्स ट्रॅक करण्यासाठी वापरला जातो.
Ethereum कॉन्ट्रॅक्ट्सच्या सर्वात मोठ्या गॅस खर्चांपैकी एक म्हणजे स्टोरेज, जे कॉन्ट्रॅक्टच्या एका कॉलपासून दुसऱ्या कॉलपर्यंत टिकून राहते. प्रत्येक स्टोरेज सेल 256 बिट्स लांब असतो. त्यामुळे तीन व्हेरिएबल्स, reserve0, reserve1, आणि blockTimestampLast, अशा प्रकारे वाटप केले जातात की एकाच स्टोरेज व्हॅल्यूमध्ये त्या तिन्हींचा समावेश होऊ शकतो (112+112+32=256).
1 uint public price0CumulativeLast;2 uint public price1CumulativeLast;हे व्हेरिएबल्स प्रत्येक टोकनसाठी (प्रत्येक दुसऱ्याच्या संदर्भात) एकत्रित खर्च धारण करतात. त्यांचा वापर ठराविक कालावधीत सरासरी एक्सचेंज रेट मोजण्यासाठी केला जाऊ शकतो.
1 uint public kLast; // reserve0 * reserve1, सर्वात अलीकडील लिक्विडिटी इव्हेंटच्या लगेच नंतरपेअर एक्सचेंज ज्या प्रकारे token0 आणि token1 मधील एक्सचेंज रेट ठरवते ते म्हणजे ट्रेड्स दरम्यान दोन रिझर्व्ह्सचा गुणाकार स्थिर ठेवणे. kLast हे ते मूल्य आहे. जेव्हा लिक्विडिटी प्रोव्हायडर टोकन्स जमा करतो किंवा काढतो तेव्हा ते बदलते आणि 0.3% मार्केट फीमुळे ते थोडे वाढते.
येथे एक साधे उदाहरण आहे. लक्षात घ्या की साधेपणासाठी टेबलमध्ये दशांश चिन्हानंतर फक्त तीन अंक आहेत आणि आम्ही 0.3% ट्रेडिंग फीकडे दुर्लक्ष करतो त्यामुळे आकडे अचूक नाहीत.
| इव्हेंट | reserve0 | reserve1 | reserve0 * reserve1 | सरासरी एक्सचेंज रेट (token1 / token0) |
|---|---|---|---|---|
| प्रारंभिक सेटअप | 1,000.000 | 1,000.000 | 1,000,000 | |
| ट्रेडर A 47.619 token1 साठी 50 token0 स्वॅप करतो | 1,050.000 | 952.381 | 1,000,000 | 0.952 |
| ट्रेडर B 8.984 token1 साठी 10 token0 स्वॅप करतो | 1,060.000 | 943.396 | 1,000,000 | 0.898 |
| ट्रेडर C 34.305 token1 साठी 40 token0 स्वॅप करतो | 1,100.000 | 909.090 | 1,000,000 | 0.858 |
| ट्रेडर D 109.01 token0 साठी 100 token1 स्वॅप करतो | 990.990 | 1,009.090 | 1,000,000 | 0.917 |
| ट्रेडर E 10.079 token1 साठी 10 token0 स्वॅप करतो | 1,000.990 | 999.010 | 1,000,000 | 1.008 |
जसे ट्रेडर्स अधिक token0 प्रदान करतात, तसे पुरवठा आणि मागणीच्या आधारावर token1 चे सापेक्ष मूल्य वाढते आणि याउलट.
लॉक
1 uint private unlocked = 1;सुरक्षा भेद्यतेचा एक वर्ग आहे जो रीएन्ट्रन्सी गैरवापरावर (opens in a new tab) आधारित आहे. Uniswap ला अनियंत्रित ERC-20 टोकन्स ट्रान्सफर करणे आवश्यक आहे, ज्याचा अर्थ अशा ERC-20 कॉन्ट्रॅक्ट्सना कॉल करणे जे त्यांना कॉल करणाऱ्या Uniswap मार्केटचा गैरवापर करण्याचा प्रयत्न करू शकतात.
कॉन्ट्रॅक्टचा भाग म्हणून unlocked व्हेरिएबल ठेवून, आपण फंक्शन्स चालू असताना (एकाच ट्रान्झॅक्शनमध्ये) त्यांना कॉल करण्यापासून रोखू शकतो.
1 modifier lock() {हे फंक्शन एक मॉडिफायर (opens in a new tab) आहे, एक फंक्शन जे सामान्य फंक्शनच्या भोवती गुंडाळले जाते जेणेकरून त्याचे वर्तन काही प्रकारे बदलता येईल.
1 require(unlocked == 1, 'UniswapV2: LOCKED');2 unlocked = 0;जर unlocked एक च्या बरोबरीचे असेल, तर ते शून्य वर सेट करा. जर ते आधीच शून्य असेल तर कॉल रिव्हर्ट करा, तो अयशस्वी करा.
1 _;मॉडिफायरमध्ये _; हा मूळ फंक्शन कॉल असतो (सर्व पॅरामीटर्ससह). येथे याचा अर्थ असा आहे की फंक्शन कॉल तेव्हाच होतो जेव्हा कॉल केला गेला तेव्हा unlocked एक होता आणि ते चालू असताना unlocked चे मूल्य शून्य असते.
1 unlocked = 1;2 }मुख्य फंक्शन परत आल्यानंतर, लॉक रिलीज करा.
संकीर्ण फंक्शन्स
1 function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {2 _reserve0 = reserve0;3 _reserve1 = reserve1;4 _blockTimestampLast = blockTimestampLast;5 }हे फंक्शन कॉलर्सना एक्सचेंजची सद्यस्थिती प्रदान करते. लक्षात घ्या की Solidity फंक्शन्स एकाधिक मूल्ये परत करू शकतात (opens in a new tab).
1 function _safeTransfer(address token, address to, uint value) private {2 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));हे अंतर्गत फंक्शन एक्सचेंजमधून दुसऱ्या कोणालातरी ERC20 टोकन्सची रक्कम ट्रान्सफर करते. SELECTOR हे निर्दिष्ट करते की आपण कॉल करत असलेले फंक्शन transfer(address,uint) आहे (वरील व्याख्या पहा).
टोकन फंक्शनसाठी इंटरफेस इम्पोर्ट करणे टाळण्यासाठी, आम्ही ABI फंक्शन्सपैकी (opens in a new tab) एकाचा वापर करून "मॅन्युअली" कॉल तयार करतो.
1 require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');2 }ERC-20 ट्रान्सफर कॉल अपयश नोंदवू शकण्याचे दोन मार्ग आहेत:
- रिव्हर्ट. जर बाह्य कॉन्ट्रॅक्टचा कॉल रिव्हर्ट झाला, तर बुलियन रिटर्न व्हॅल्यू
falseअसते - सामान्यपणे समाप्त करा परंतु अपयश नोंदवा. त्या बाबतीत रिटर्न व्हॅल्यू बफरची लांबी नॉन-झिरो असते आणि जेव्हा बुलियन व्हॅल्यू म्हणून डीकोड केले जाते तेव्हा ते
falseअसते
यापैकी कोणतीही परिस्थिती उद्भवल्यास, रिव्हर्ट करा.
इव्हेंट्स
1 event Mint(address indexed sender, uint amount0, uint amount1);2 event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);जेव्हा लिक्विडिटी प्रोव्हायडर एकतर लिक्विडिटी जमा करतो (Mint) किंवा ती काढतो (Burn) तेव्हा हे दोन इव्हेंट्स एमिट केले जातात. दोन्ही प्रकरणांमध्ये, जमा केलेल्या किंवा काढलेल्या token0 आणि token1 ची रक्कम इव्हेंटचा भाग असते, तसेच आम्हाला कॉल करणाऱ्या अकाउंटची ओळख (sender). पैसे काढण्याच्या बाबतीत, इव्हेंटमध्ये टोकन्स प्राप्त करणारे लक्ष्य (to) देखील समाविष्ट असते, जे सेंडरसारखे नसू शकते.
1 event Swap(2 address indexed sender,3 uint amount0In,4 uint amount1In,5 uint amount0Out,6 uint amount1Out,7 address indexed to8 );जेव्हा एखादा ट्रेडर एका टोकनची दुसऱ्या टोकनसाठी देवाणघेवाण करतो तेव्हा हा इव्हेंट एमिट केला जातो. पुन्हा, सेंडर आणि डेस्टिनेशन समान नसू शकतात. प्रत्येक टोकन एकतर एक्सचेंजला पाठवले जाऊ शकते किंवा त्यातून प्राप्त केले जाऊ शकते.
1 event Sync(uint112 reserve0, uint112 reserve1);शेवटी, नवीनतम रिझर्व्ह माहिती (आणि म्हणून एक्सचेंज रेट) प्रदान करण्यासाठी, कारण काहीही असले तरी, प्रत्येक वेळी टोकन्स जोडले किंवा काढले जातात तेव्हा Sync एमिट केले जाते.
सेटअप फंक्शन्स
जेव्हा नवीन पेअर एक्सचेंज सेट केले जाते तेव्हा ही फंक्शन्स एकदा कॉल केली जावीत.
1 constructor() public {2 factory = msg.sender;3 }कन्स्ट्रक्टर हे सुनिश्चित करतो की आम्ही पेअर तयार करणाऱ्या फॅक्टरीच्या पत्त्याचा मागोवा ठेवू. ही माहिती initialize साठी आणि फॅक्टरी फीसाठी (जर अस्तित्वात असेल तर) आवश्यक आहे
1 // डिप्लॉयमेंटच्या वेळी फॅक्टरीद्वारे एकदाच कॉल केले जाते2 function initialize(address _token0, address _token1) external {3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // पुरेशी तपासणी4 token0 = _token0;5 token1 = _token1;6 }हे फंक्शन फॅक्टरीला (आणि फक्त फॅक्टरीला) दोन ERC-20 टोकन्स निर्दिष्ट करण्याची परवानगी देते ज्यांची ही पेअर देवाणघेवाण करेल.
अंतर्गत अपडेट फंक्शन्स
_update
1 // रिझर्व्ह अपडेट करा आणि, प्रति ब्लॉक पहिल्या कॉलवर, प्राईस अॅक्युम्युलेटर्स अपडेट करा2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {प्रत्येक वेळी टोकन्स जमा केल्यावर किंवा काढल्यावर हे फंक्शन कॉल केले जाते.
1 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 पर्यंत मर्यादित आहे. आतापर्यंत ही समस्या उद्भवलेली नाही.
1 uint32 blockTimestamp = uint32(block.timestamp % 2**32);2 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // ओव्हरफ्लो अपेक्षित आहे3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {जर गेलेला वेळ शून्य नसेल, तर याचा अर्थ आपण या ब्लॉकवरील पहिले एक्सचेंज ट्रान्झॅक्शन आहोत. त्या बाबतीत, आम्हाला कॉस्ट ॲक्युम्युलेटर्स अपडेट करणे आवश्यक आहे.
1 // * कधीही ओव्हरफ्लो होत नाही, आणि + ओव्हरफ्लो अपेक्षित आहे2 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;3 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;4 }प्रत्येक कॉस्ट ॲक्युम्युलेटर नवीनतम कॉस्ट (दुसऱ्या टोकनचा रिझर्व्ह/या टोकनचा रिझर्व्ह) गुणिले सेकंदांमध्ये गेलेला वेळ यासह अपडेट केला जातो. सरासरी किंमत मिळवण्यासाठी, तुम्ही वेळेच्या दोन बिंदूंमधील एकत्रित किंमत वाचता आणि त्यांच्यातील वेळेच्या फरकाने भागता. उदाहरणार्थ, इव्हेंट्सचा हा क्रम गृहीत धरा:
| इव्हेंट | reserve0 | reserve1 | टाइमस्टॅम्प | मार्जिनल एक्सचेंज रेट (reserve1 / reserve0) | price0CumulativeLast |
|---|---|---|---|---|---|
| प्रारंभिक सेटअप | 1,000.000 | 1,000.000 | 5,000 | 1.000 | 0 |
| ट्रेडर A 50 token0 जमा करतो आणि 47.619 token1 परत मिळवतो | 1,050.000 | 952.381 | 5,020 | 0.907 | 20 |
| ट्रेडर B 10 token0 जमा करतो आणि 8.984 token1 परत मिळवतो | 1,060.000 | 943.396 | 5,030 | 0.890 | 20+10*0.907 = 29.07 |
| ट्रेडर C 40 token0 जमा करतो आणि 34.305 token1 परत मिळवतो | 1,100.000 | 909.090 | 5,100 | 0.826 | 29.07+70*0.890 = 91.37 |
| ट्रेडर D 100 token1 जमा करतो आणि 109.01 token0 परत मिळवतो | 990.990 | 1,009.090 | 5,110 | 1.018 | 91.37+10*0.826 = 99.63 |
| ट्रेडर E 10 token0 जमा करतो आणि 10.079 token1 परत मिळवतो | 1,000.990 | 999.010 | 5,150 | 0.998 | 99.63+40*1.1018 = 143.702 |
समजा आपल्याला 5,030 आणि 5,150 या टाइमस्टॅम्प्स दरम्यान Token0 ची सरासरी किंमत मोजायची आहे. price0Cumulative च्या मूल्यातील फरक 143.702-29.07=114.632 आहे. ही दोन मिनिटांची (120 सेकंद) सरासरी आहे. त्यामुळे सरासरी किंमत 114.632/120 = 0.955 आहे.
या किंमतीच्या गणनेमुळेच आपल्याला जुन्या रिझर्व्ह आकारांची माहिती असणे आवश्यक आहे.
1 reserve0 = uint112(balance0);2 reserve1 = uint112(balance1);3 blockTimestampLast = blockTimestamp;4 emit Sync(reserve0, reserve1);5 }शेवटी, ग्लोबल व्हेरिएबल्स अपडेट करा आणि Sync इव्हेंट एमिट करा.
_mintFee
1 // जर फी चालू असेल, तर sqrt(k) मधील वाढीच्या 1/6 च्या समतुल्य लिक्विडिटी मिंट करा2 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {Uniswap 2.0 मध्ये ट्रेडर्स मार्केट वापरण्यासाठी 0.30% फी भरतात. त्या फीचा बहुतांश भाग (ट्रेडचा 0.25%) नेहमी लिक्विडिटी प्रोव्हायडर्सना जातो. उर्वरित 0.05% एकतर लिक्विडिटी प्रोव्हायडर्सना किंवा फॅक्टरीद्वारे प्रोटोकॉल फी म्हणून निर्दिष्ट केलेल्या पत्त्यावर जाऊ शकतो, जो Uniswap ला त्यांच्या विकास प्रयत्नांसाठी पैसे देतो.
गणने कमी करण्यासाठी (आणि त्यामुळे गॅस खर्च), ही फी प्रत्येक ट्रान्झॅक्शनच्या ऐवजी केवळ पूलमधून लिक्विडिटी जोडली किंवा काढली जाते तेव्हाच मोजली जाते.
1 address feeTo = IUniswapV2Factory(factory).feeTo();2 feeOn = feeTo != address(0);फॅक्टरीचे फी डेस्टिनेशन वाचा. जर ते शून्य असेल तर कोणतीही प्रोटोकॉल फी नाही आणि ती फी मोजण्याची गरज नाही.
1 uint _kLast = kLast; // गॅसची बचतkLast स्टेट व्हेरिएबल स्टोरेजमध्ये स्थित आहे, त्यामुळे कॉन्ट्रॅक्टच्या वेगवेगळ्या कॉल्स दरम्यान त्याचे मूल्य असेल.
स्टोरेजचा ॲक्सेस हा व्होलॅटाईल मेमरीच्या ॲक्सेसपेक्षा खूप महाग आहे जी कॉन्ट्रॅक्टचा फंक्शन कॉल संपल्यावर रिलीज होते, त्यामुळे गॅस वाचवण्यासाठी आम्ही अंतर्गत व्हेरिएबल वापरतो.
1 if (feeOn) {2 if (_kLast != 0) {लिक्विडिटी प्रोव्हायडर्सना त्यांचा वाटा केवळ त्यांच्या लिक्विडिटी टोकन्सच्या वाढीमुळे मिळतो. परंतु प्रोटोकॉल फीसाठी नवीन लिक्विडिटी टोकन्स मिंट करणे आणि feeTo पत्त्यावर प्रदान करणे आवश्यक आहे.
1 uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));2 uint rootKLast = Math.sqrt(_kLast);3 if (rootK > rootKLast) {जर नवीन लिक्विडिटी असेल ज्यावर प्रोटोकॉल फी गोळा करायची असेल. तुम्ही स्क्वेअर रूट फंक्शन या लेखात पुढे पाहू शकता
1 uint numerator = totalSupply.mul(rootK.sub(rootKLast));2 uint denominator = rootK.mul(5).add(rootKLast);3 uint liquidity = numerator / denominator;फीची ही गुंतागुंतीची गणना व्हाईटपेपरमध्ये (opens in a new tab) पृष्ठ 5 वर स्पष्ट केली आहे. आम्हाला माहित आहे की kLast मोजल्या गेलेल्या वेळेपासून ते आतापर्यंत कोणतीही लिक्विडिटी जोडली किंवा काढली गेली नाही (कारण आम्ही प्रत्येक वेळी लिक्विडिटी जोडली किंवा काढली जाते तेव्हा, ती प्रत्यक्षात बदलण्यापूर्वी ही गणना चालवतो), त्यामुळे reserve0 * reserve1 मधील कोणताही बदल ट्रान्झॅक्शन फीमधून आला पाहिजे (त्यांच्याशिवाय आम्ही reserve0 * reserve1 स्थिर ठेवू).
1 if (liquidity > 0) _mint(feeTo, liquidity);2 }3 }अतिरिक्त लिक्विडिटी टोकन्स प्रत्यक्षात तयार करण्यासाठी आणि त्यांना feeTo ला नियुक्त करण्यासाठी UniswapV2ERC20._mint फंक्शन वापरा.
1 } else if (_kLast != 0) {2 kLast = 0;3 }4 }जर कोणतीही फी नसेल तर kLast शून्य वर सेट करा (जर ते आधीच नसेल तर). जेव्हा हे कॉन्ट्रॅक्ट लिहिले गेले तेव्हा एक गॅस रिफंड वैशिष्ट्य (opens in a new tab) होते ज्याने कॉन्ट्रॅक्ट्सना त्यांना आवश्यक नसलेले स्टोरेज शून्य करून Ethereum स्टेटचा एकूण आकार कमी करण्यास प्रोत्साहित केले.
शक्य असेल तेव्हा या कोडला तो रिफंड मिळतो.
बाह्यरित्या ॲक्सेसिबल फंक्शन्स
लक्षात घ्या की कोणतेही ट्रान्झॅक्शन किंवा कॉन्ट्रॅक्ट या फंक्शन्सना कॉल करू शकते, तरीही ते पेरिफेरी कॉन्ट्रॅक्टमधून कॉल करण्यासाठी डिझाइन केलेले आहेत. जर तुम्ही त्यांना थेट कॉल केले तर तुम्ही पेअर एक्सचेंजला फसवू शकणार नाही, परंतु तुम्ही चुकीमुळे मूल्य गमावू शकता.
mint
1 // हे लो-लेव्हल फंक्शन अशा कॉन्ट्रॅक्टमधून कॉल केले जावे जे महत्त्वाच्या सुरक्षितता तपासण्या करते2 function mint(address to) external lock returns (uint liquidity) {जेव्हा लिक्विडिटी प्रोव्हायडर पूलमध्ये लिक्विडिटी जोडतो तेव्हा हे फंक्शन कॉल केले जाते. हे बक्षीस म्हणून अतिरिक्त लिक्विडिटी टोकन्स मिंट करते. हे पेरिफेरी कॉन्ट्रॅक्टमधून कॉल केले जावे जे एकाच ट्रान्झॅक्शनमध्ये लिक्विडिटी जोडल्यानंतर त्याला कॉल करते (जेणेकरून कायदेशीर मालकापूर्वी नवीन लिक्विडिटीचा दावा करणारे ट्रान्झॅक्शन इतर कोणीही सबमिट करू शकणार नाही).
1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // गॅसची बचतएकाधिक मूल्ये परत करणाऱ्या Solidity फंक्शनचे परिणाम वाचण्याचा हा मार्ग आहे. आम्ही शेवटची परत केलेली मूल्ये, ब्लॉक टाइमस्टॅम्प, टाकून देतो कारण आम्हाला त्याची आवश्यकता नाही.
1 uint balance0 = IERC20(token0).balanceOf(address(this));2 uint balance1 = IERC20(token1).balanceOf(address(this));3 uint amount0 = balance0.sub(_reserve0);4 uint amount1 = balance1.sub(_reserve1);सध्याचे बॅलन्सेस मिळवा आणि प्रत्येक टोकन प्रकाराचे किती जोडले गेले ते पहा.
1 bool feeOn = _mintFee(_reserve0, _reserve1);गोळा करण्यासाठी प्रोटोकॉल फीची गणना करा, जर काही असेल तर, आणि त्यानुसार लिक्विडिटी टोकन्स मिंट करा. कारण _mintFee चे पॅरामीटर्स जुनी रिझर्व्ह मूल्ये आहेत, फीची गणना केवळ फीमुळे होणाऱ्या पूल बदलांवर आधारित अचूकपणे केली जाते.
1 uint _totalSupply = totalSupply; // गॅसची बचत, येथे परिभाषित करणे आवश्यक आहे कारण totalSupply _mintFee मध्ये अपडेट होऊ शकते2 if (_totalSupply == 0) {3 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);4 _mint(address(0), MINIMUM_LIQUIDITY); // पहिले MINIMUM_LIQUIDITY टोकन्स कायमचे लॉक कराजर ही पहिली ठेव असेल, तर MINIMUM_LIQUIDITY टोकन्स तयार करा आणि त्यांना लॉक करण्यासाठी ॲड्रेस झिरोवर पाठवा. ते कधीही रिडीम केले जाऊ शकत नाहीत, ज्याचा अर्थ पूल कधीही पूर्णपणे रिकामा होणार नाही (हे आम्हाला काही ठिकाणी शून्याने भाग जाण्यापासून वाचवते). MINIMUM_LIQUIDITY चे मूल्य एक हजार आहे, जे बहुतेक ERC-20 टोकनच्या 10^-18 व्या युनिट्समध्ये विभागलेले आहेत हे लक्षात घेता, जसे ETH wei मध्ये विभागले जाते, एकाच टोकनच्या मूल्याच्या 10^-15 आहे. जास्त किंमत नाही.
पहिल्या ठेवीच्या वेळी आम्हाला दोन टोकन्सचे सापेक्ष मूल्य माहित नसते, म्हणून आम्ही फक्त रकमांचा गुणाकार करतो आणि स्क्वेअर रूट घेतो, असे गृहीत धरून की ठेव आम्हाला दोन्ही टोकन्समध्ये समान मूल्य प्रदान करते.
आम्ही यावर विश्वास ठेवू शकतो कारण आर्बिट्रेजमुळे मूल्य गमावणे टाळण्यासाठी समान मूल्य प्रदान करणे ठेवीदाराच्या हिताचे आहे. समजा दोन टोकन्सचे मूल्य समान आहे, परंतु आमच्या ठेवीदाराने Token0 च्या तुलनेत Token1 ची चार पट जास्त ठेव ठेवली. पेअर एक्सचेंजला असे वाटते की Token0 अधिक मौल्यवान आहे या वस्तुस्थितीचा वापर करून ट्रेडर त्यातून मूल्य काढू शकतो.
| इव्हेंट | reserve0 | reserve1 | reserve0 * reserve1 | पूलचे मूल्य (reserve0 + reserve1) |
|---|---|---|---|---|
| प्रारंभिक सेटअप | 8 | 32 | 256 | 40 |
| ट्रेडर 8 Token0 टोकन्स जमा करतो, 16 Token1 परत मिळवतो | 16 | 16 | 256 | 32 |
जसे तुम्ही पाहू शकता, ट्रेडरने अतिरिक्त 8 टोकन्स मिळवले, जे पूलच्या मूल्यातील कपातीतून येतात, ज्यामुळे त्याच्या मालक असलेल्या ठेवीदाराचे नुकसान होते.
1 } else {2 liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);प्रत्येक त्यानंतरच्या ठेवीसह आम्हाला दोन ॲसेट्समधील एक्सचेंज रेट आधीच माहित असतो आणि आम्ही लिक्विडिटी प्रोव्हायडर्सनी दोन्हीमध्ये समान मूल्य प्रदान करण्याची अपेक्षा करतो. जर त्यांनी तसे केले नाही, तर आम्ही त्यांना शिक्षा म्हणून त्यांनी प्रदान केलेल्या कमी मूल्यावर आधारित लिक्विडिटी टोकन्स देतो.
ती प्रारंभिक ठेव असो किंवा त्यानंतरची, आम्ही प्रदान करत असलेल्या लिक्विडिटी टोकन्सची संख्या reserve0*reserve1 मधील बदलाच्या स्क्वेअर रूटच्या बरोबरीची असते आणि लिक्विडिटी टोकनचे मूल्य बदलत नाही (जोपर्यंत आम्हाला अशी ठेव मिळत नाही ज्यामध्ये दोन्ही प्रकारांची समान मूल्ये नाहीत, अशा परिस्थितीत "दंड" वितरित केला जातो). येथे समान मूल्य असलेल्या दोन टोकन्सचे आणखी एक उदाहरण आहे, ज्यामध्ये तीन चांगल्या ठेवी आणि एक वाईट ठेव आहे (केवळ एका टोकन प्रकाराची ठेव, त्यामुळे ती कोणतीही लिक्विडिटी टोकन्स तयार करत नाही).
| इव्हेंट | reserve0 | reserve1 | reserve0 * reserve1 | पूल मूल्य (reserve0 + reserve1) | या ठेवीसाठी मिंट केलेले लिक्विडिटी टोकन्स | एकूण लिक्विडिटी टोकन्स | प्रत्येक लिक्विडिटी टोकनचे मूल्य |
|---|---|---|---|---|---|---|---|
| प्रारंभिक सेटअप | 8.000 | 8.000 | 64 | 16.000 | 8 | 8 | 2.000 |
| प्रत्येक प्रकाराचे चार जमा करा | 12.000 | 12.000 | 144 | 24.000 | 4 | 12 | 2.000 |
| प्रत्येक प्रकाराचे दोन जमा करा | 14.000 | 14.000 | 196 | 28.000 | 2 | 14 | 2.000 |
| असमान मूल्य ठेव | 18.000 | 14.000 | 252 | 32.000 | 0 | 14 | ~2.286 |
| आर्बिट्रेजनंतर | ~15.874 | ~15.874 | 252 | ~31.748 | 0 | 14 | ~2.267 |
1 }2 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');3 _mint(to, liquidity);अतिरिक्त लिक्विडिटी टोकन्स प्रत्यक्षात तयार करण्यासाठी आणि त्यांना योग्य अकाउंटला देण्यासाठी UniswapV2ERC20._mint फंक्शन वापरा.
12 _update(balance0, balance1, _reserve0, _reserve1);3 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 आणि reserve1 अद्ययावत आहेत4 emit Mint(msg.sender, amount0, amount1);5 }स्टेट व्हेरिएबल्स (reserve0, reserve1, आणि आवश्यक असल्यास kLast) अपडेट करा आणि योग्य इव्हेंट एमिट करा.
burn
1 // हे लो-लेव्हल फंक्शन अशा कॉन्ट्रॅक्टमधून कॉल केले जावे जे महत्त्वाच्या सुरक्षितता तपासण्या करते2 function burn(address to) external lock returns (uint amount0, uint amount1) {जेव्हा लिक्विडिटी काढली जाते आणि योग्य लिक्विडिटी टोकन्स बर्न करणे आवश्यक असते तेव्हा हे फंक्शन कॉल केले जाते. हे पेरिफेरी अकाउंटमधून देखील कॉल केले जावे.
1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // गॅसची बचत2 address _token0 = token0; // गॅसची बचत3 address _token1 = token1; // गॅसची बचत4 uint balance0 = IERC20(_token0).balanceOf(address(this));5 uint balance1 = IERC20(_token1).balanceOf(address(this));6 uint liquidity = balanceOf[address(this)];पेरिफेरी कॉन्ट्रॅक्टने कॉल करण्यापूर्वी बर्न करायची लिक्विडिटी या कॉन्ट्रॅक्टमध्ये ट्रान्सफर केली. अशा प्रकारे आम्हाला माहित आहे की किती लिक्विडिटी बर्न करायची आहे आणि आम्ही खात्री करू शकतो की ती बर्न होईल.
1 bool feeOn = _mintFee(_reserve0, _reserve1);2 uint _totalSupply = totalSupply; // गॅसची बचत, येथे परिभाषित करणे आवश्यक आहे कारण totalSupply _mintFee मध्ये अपडेट होऊ शकते3 amount0 = liquidity.mul(balance0) / _totalSupply; // बॅलन्स वापरल्याने प्रमाणबद्ध (pro-rata) वितरण सुनिश्चित होते4 amount1 = liquidity.mul(balance1) / _totalSupply; // बॅलन्स वापरल्याने प्रमाणबद्ध (pro-rata) वितरण सुनिश्चित होते5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');लिक्विडिटी प्रोव्हायडरला दोन्ही टोकन्सचे समान मूल्य मिळते. अशा प्रकारे आम्ही एक्सचेंज रेट बदलत नाही.
1 _burn(address(this), liquidity);2 _safeTransfer(_token0, to, amount0);3 _safeTransfer(_token1, to, amount1);4 balance0 = IERC20(_token0).balanceOf(address(this));5 balance1 = IERC20(_token1).balanceOf(address(this));67 _update(balance0, balance1, _reserve0, _reserve1);8 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 आणि reserve1 अद्ययावत आहेत9 emit Burn(msg.sender, amount0, amount1, to);10 }11सर्व दाखवाउर्वरित burn फंक्शन हे वरील mint फंक्शनची मिरर इमेज आहे.
swap
1 // हे लो-लेव्हल फंक्शन अशा कॉन्ट्रॅक्टमधून कॉल केले जावे जे महत्त्वाच्या सुरक्षितता तपासण्या करते2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {हे फंक्शन देखील पेरिफेरी कॉन्ट्रॅक्टमधून कॉल केले जावे असे अपेक्षित आहे.
1 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');2 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // गॅसची बचत3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');45 uint balance0;6 uint balance1;7 { // _token{0,1} साठी स्कोप, 'stack too deep' एरर्स टाळतोलोकल व्हेरिएबल्स एकतर मेमरीमध्ये किंवा, जर ते खूप जास्त नसतील तर, थेट स्टॅकवर स्टोअर केले जाऊ शकतात. जर आपण संख्या मर्यादित करू शकलो जेणेकरून आपण स्टॅक वापरू तर आपण कमी गॅस वापरू. अधिक तपशीलांसाठी यलो पेपर, औपचारिक Ethereum स्पेसिफिकेशन्स (opens in a new tab), पृष्ठ 26, समीकरण 298 पहा.
1 address _token0 = token0;2 address _token1 = token1;3 require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');4 if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // आशावादीपणे (optimistically) टोकन्स ट्रान्सफर करा5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // आशावादीपणे (optimistically) टोकन्स ट्रान्सफर कराहे ट्रान्सफर आशावादी आहे, कारण सर्व अटी पूर्ण झाल्याची खात्री होण्यापूर्वी आम्ही ट्रान्सफर करतो. Ethereum मध्ये हे ठीक आहे कारण जर कॉलमध्ये नंतर अटी पूर्ण झाल्या नाहीत तर आम्ही त्यातून आणि त्याने तयार केलेल्या कोणत्याही बदलांमधून रिव्हर्ट करतो.
1 if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);विनंती केल्यास प्राप्तकर्त्याला स्वॅपबद्दल माहिती द्या.
1 balance0 = IERC20(_token0).balanceOf(address(this));2 balance1 = IERC20(_token1).balanceOf(address(this));3 }सध्याचे बॅलन्सेस मिळवा. पेरिफेरी कॉन्ट्रॅक्ट आम्हाला स्वॅपसाठी कॉल करण्यापूर्वी टोकन्स पाठवते. यामुळे कॉन्ट्रॅक्टला फसवले जात नाही हे तपासणे सोपे होते, एक तपासणी जी कोर कॉन्ट्रॅक्टमध्ये होणे आवश्यक आहे (कारण आम्हाला आमच्या पेरिफेरी कॉन्ट्रॅक्ट व्यतिरिक्त इतर संस्थांद्वारे कॉल केले जाऊ शकते).
1 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;2 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;3 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');4 { // reserve{0,1}Adjusted साठी स्कोप, 'stack too deep' एरर्स टाळतो5 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));6 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));7 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');स्वॅपमधून आमचे नुकसान होणार नाही याची खात्री करण्यासाठी ही एक सॅनिटी चेक आहे. अशी कोणतीही परिस्थिती नाही ज्यामध्ये स्वॅपने reserve0*reserve1 कमी केले पाहिजे. येथेच आम्ही हे देखील सुनिश्चित करतो की स्वॅपवर 0.3% फी पाठविली जात आहे; K च्या मूल्याची सॅनिटी तपासणी करण्यापूर्वी, आम्ही दोन्ही बॅलन्सेसना 1000 ने गुणतो आणि त्यातून 3 ने गुणलेल्या रकमा वजा करतो, याचा अर्थ 0.3% (3/1000 = 0.003 = 0.3%) बॅलन्समधून वजा केले जात आहे त्याच्या K मूल्याची सध्याच्या रिझर्व्ह्सच्या K मूल्याशी तुलना करण्यापूर्वी.
1 }23 _update(balance0, balance1, _reserve0, _reserve1);4 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);5 }reserve0 आणि reserve1 अपडेट करा, आणि आवश्यक असल्यास प्राईस ॲक्युम्युलेटर्स आणि टाइमस्टॅम्प अपडेट करा आणि इव्हेंट एमिट करा.
Sync किंवा Skim
पेअर एक्सचेंजला वाटत असलेल्या रिझर्व्ह्सच्या तुलनेत वास्तविक बॅलन्सेस सिंकच्या बाहेर जाणे शक्य आहे.
कॉन्ट्रॅक्टच्या संमतीशिवाय टोकन्स काढण्याचा कोणताही मार्ग नाही, परंतु ठेवी ही वेगळी बाब आहे. एखादे अकाउंट mint किंवा swap ला कॉल न करता एक्सचेंजमध्ये टोकन्स ट्रान्सफर करू शकते.
त्या बाबतीत दोन उपाय आहेत:
sync, रिझर्व्ह्सना सध्याच्या बॅलन्सेसवर अपडेट कराskim, अतिरिक्त रक्कम काढा. लक्षात घ्या की कोणत्याही अकाउंटलाskimकॉल करण्याची परवानगी आहे कारण आम्हाला माहित नाही की टोकन्स कोणी जमा केले. ही माहिती इव्हेंटमध्ये एमिट केली जाते, परंतु इव्हेंट्स ब्लॉकचेनवरून ॲक्सेसिबल नसतात.
1 // बॅलन्सला रिझर्व्हशी जुळण्यासाठी सक्ती करा2 function skim(address to) external lock {3 address _token0 = token0; // गॅसची बचत4 address _token1 = token1; // गॅसची बचत5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));7 }891011 // रिझर्व्हला बॅलन्सशी जुळण्यासाठी सक्ती करा12 function sync() external lock {13 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);14 }15}सर्व दाखवाUniswapV2Factory.sol
हे कॉन्ट्रॅक्ट (opens in a new tab) पेअर एक्सचेंजेस तयार करते.
1pragma solidity =0.5.16;23import './interfaces/IUniswapV2Factory.sol';4import './UniswapV2Pair.sol';56contract UniswapV2Factory is IUniswapV2Factory {7 address public feeTo;8 address public feeToSetter;प्रोटोकॉल फी लागू करण्यासाठी हे स्टेट व्हेरिएबल्स आवश्यक आहेत (व्हाईटपेपर (opens in a new tab), पृष्ठ 5 पहा).
feeTo पत्ता प्रोटोकॉल फीसाठी लिक्विडिटी टोकन्स जमा करतो आणि feeToSetter हा असा पत्ता आहे ज्याला feeTo वेगळ्या पत्त्यावर बदलण्याची परवानगी आहे.
1 mapping(address => mapping(address => address)) public getPair;2 address[] public allPairs;हे व्हेरिएबल्स पेअर्सचा, दोन टोकन प्रकारांमधील एक्सचेंजेसचा मागोवा ठेवतात.
पहिले, getPair, एक मॅपिंग आहे जे पेअर एक्सचेंज कॉन्ट्रॅक्ट ओळखते ते एक्सचेंज करत असलेल्या दोन ERC-20 टोकन्सच्या आधारावर. ERC-20 टोकन्स त्यांची अंमलबजावणी करणाऱ्या कॉन्ट्रॅक्ट्सच्या पत्त्यांद्वारे ओळखले जातात, त्यामुळे कीज आणि व्हॅल्यू हे सर्व पत्ते आहेत. पेअर एक्सचेंजचा पत्ता मिळवण्यासाठी जो तुम्हाला tokenA मधून tokenB मध्ये रूपांतरित करू देतो, तुम्ही getPair[<tokenA address>][<tokenB address>] (किंवा उलट) वापरता.
दुसरे व्हेरिएबल, allPairs, एक ॲरे आहे ज्यामध्ये या फॅक्टरीद्वारे तयार केलेल्या पेअर एक्सचेंजेसचे सर्व पत्ते समाविष्ट आहेत. Ethereum मध्ये तुम्ही मॅपिंगच्या सामग्रीवर इटरेट करू शकत नाही किंवा सर्व कीजची सूची मिळवू शकत नाही, त्यामुळे ही फॅक्टरी कोणते एक्सचेंजेस व्यवस्थापित करते हे जाणून घेण्याचा हा व्हेरिएबल एकमेव मार्ग आहे.
टीप: तुम्ही मॅपिंगच्या सर्व कीजवर इटरेट करू शकत नाही याचे कारण असे आहे की कॉन्ट्रॅक्ट डेटा स्टोरेज महाग आहे, त्यामुळे आपण त्याचा जितका कमी वापर करू तितके चांगले आणि आपण ते जितक्या कमी वेळा बदलू तितके चांगले. तुम्ही इटरेशनला सपोर्ट करणारी मॅपिंग्ज (opens in a new tab) तयार करू शकता, परंतु त्यांना कीजच्या सूचीसाठी अतिरिक्त स्टोरेजची आवश्यकता असते. बहुतेक ॲप्लिकेशन्समध्ये तुम्हाला त्याची आवश्यकता नसते.
1 event PairCreated(address indexed token0, address indexed token1, address pair, uint);जेव्हा नवीन पेअर एक्सचेंज तयार केले जाते तेव्हा हा इव्हेंट एमिट केला जातो. यामध्ये टोकन्सचे पत्ते, पेअर एक्सचेंजचा पत्ता आणि फॅक्टरीद्वारे व्यवस्थापित केलेल्या एक्सचेंजेसची एकूण संख्या समाविष्ट असते.
1 constructor(address _feeToSetter) public {2 feeToSetter = _feeToSetter;3 }कन्स्ट्रक्टर फक्त एकच गोष्ट करतो ती म्हणजे feeToSetter निर्दिष्ट करणे. फॅक्टरीज फीशिवाय सुरू होतात आणि फक्त feeSetter ते बदलू शकतो.
1 function allPairsLength() external view returns (uint) {2 return allPairs.length;3 }हे फंक्शन एक्सचेंज पेअर्सची संख्या परत करते.
1 function createPair(address tokenA, address tokenB) external returns (address pair) {दोन ERC-20 टोकन्समध्ये पेअर एक्सचेंज तयार करणे हे फॅक्टरीचे मुख्य फंक्शन आहे. लक्षात घ्या की कोणीही या फंक्शनला कॉल करू शकतो. नवीन पेअर एक्सचेंज तयार करण्यासाठी तुम्हाला Uniswap च्या परवानगीची आवश्यकता नाही.
1 require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');2 (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);आम्हाला नवीन एक्सचेंजचा पत्ता डिटरमिनिस्टिक असावा असे वाटते, जेणेकरून त्याची आगाऊ ऑफचेन गणना केली जाऊ शकेल (हे लेयर 2 ट्रान्झॅक्शन्ससाठी उपयुक्त ठरू शकते). हे करण्यासाठी आम्हाला टोकन पत्त्यांचा एक सुसंगत क्रम असणे आवश्यक आहे, आम्ही ते कोणत्या क्रमाने प्राप्त केले आहेत याची पर्वा न करता, म्हणून आम्ही त्यांना येथे सॉर्ट करतो.
1 require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');2 require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // एकच तपासणी पुरेशी आहेमोठे लिक्विडिटी पूल्स लहान पूल्सपेक्षा चांगले असतात, कारण त्यांच्या किंमती अधिक स्थिर असतात. आम्हाला टोकन्सच्या प्रति पेअर एकापेक्षा जास्त लिक्विडिटी पूल नको आहे. जर आधीच एक्सचेंज असेल, तर त्याच पेअरसाठी दुसरे तयार करण्याची गरज नाही.
1 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).
1 bytes32 salt = keccak256(abi.encodePacked(token0, token1));2 assembly {3 pair := create2(0, add(bytecode, 32), mload(bytecode), salt)4 }जेव्हा एखाद्या ऑपकोडला Solidity द्वारे अद्याप सपोर्ट नसतो तेव्हा आम्ही इनलाइन असेंब्ली (opens in a new tab) वापरून त्याला कॉल करू शकतो.
1 IUniswapV2Pair(pair).initialize(token0, token1);नवीन एक्सचेंजला ते कोणते दोन टोकन्स एक्सचेंज करते हे सांगण्यासाठी initialize फंक्शनला कॉल करा.
1 getPair[token0][token1] = pair;2 getPair[token1][token0] = pair; // उलट्या दिशेने मॅपिंग पॉप्युलेट करा3 allPairs.push(pair);4 emit PairCreated(token0, token1, pair, allPairs.length);5 }नवीन पेअरची माहिती स्टेट व्हेरिएबल्समध्ये सेव्ह करा आणि जगाला नवीन पेअर एक्सचेंजची माहिती देण्यासाठी इव्हेंट एमिट करा.
1 function setFeeTo(address _feeTo) external {2 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');3 feeTo = _feeTo;4 }56 function setFeeToSetter(address _feeToSetter) external {7 require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');8 feeToSetter = _feeToSetter;9 }10}सर्व दाखवाही दोन फंक्शन्स feeSetter ला फी प्राप्तकर्त्यावर (जर कोणी असेल तर) नियंत्रण ठेवण्याची आणि feeSetter ला नवीन पत्त्यावर बदलण्याची परवानगी देतात.
UniswapV2ERC20.sol
हे कॉन्ट्रॅक्ट (opens in a new tab) ERC-20 लिक्विडिटी टोकन लागू करते. हे OpenZeppelin ERC-20 कॉन्ट्रॅक्ट सारखेच आहे, त्यामुळे मी फक्त तो भाग स्पष्ट करेन जो वेगळा आहे, permit कार्यक्षमता.
Ethereum वरील ट्रान्झॅक्शन्ससाठी इथर (ETH) खर्च होतो, जे खऱ्या पैशाच्या समतुल्य आहे. जर तुमच्याकडे ERC-20 टोकन्स असतील पण ETH नसेल, तर तुम्ही ट्रान्झॅक्शन्स पाठवू शकत नाही, त्यामुळे तुम्ही त्यांच्यासोबत काहीही करू शकत नाही. ही समस्या टाळण्यासाठी एक उपाय म्हणजे मेटा-ट्रान्झॅक्शन्स (opens in a new tab). टोकन्सचा मालक अशा ट्रान्झॅक्शनवर स्वाक्षरी करतो जे दुसऱ्या कोणालातरी ऑफचेन टोकन्स काढण्याची परवानगी देते आणि इंटरनेटचा वापर करून ते प्राप्तकर्त्याला पाठवते. प्राप्तकर्ता, ज्याच्याकडे ETH आहे, तो नंतर मालकाच्या वतीने परमिट सबमिट करतो.
1 bytes32 public DOMAIN_SEPARATOR;2 // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");3 bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;हा हॅश ट्रान्झॅक्शन प्रकारासाठी आयडेंटिफायर (opens in a new tab) आहे. आम्ही येथे सपोर्ट करत असलेला एकमेव प्रकार या पॅरामीटर्ससह Permit आहे.
1 mapping(address => uint) public nonces;प्राप्तकर्त्यासाठी डिजिटल स्वाक्षरी बनावट करणे शक्य नाही. तथापि, तेच ट्रान्झॅक्शन दोनदा पाठवणे क्षुल्लक आहे (हा रिप्ले अटॅकचा (opens in a new tab) एक प्रकार आहे). हे टाळण्यासाठी, आम्ही नॉन्स (opens in a new tab) वापरतो. जर नवीन Permit चा नॉन्स वापरलेल्या शेवटच्या नॉन्सपेक्षा एकने जास्त नसेल, तर आम्ही ते अवैध असल्याचे गृहीत धरतो.
1 constructor() public {2 uint chainId;3 assembly {4 chainId := chainid5 }हा चेन आयडेंटिफायर (opens in a new tab) पुनर्प्राप्त करण्यासाठीचा कोड आहे. हे Yul (opens in a new tab) नावाचा EVM असेंब्ली डायलेक्ट वापरते. लक्षात घ्या की Yul च्या सध्याच्या आवृत्तीमध्ये तुम्हाला chainid() वापरावे लागेल, chainid नाही.
1 DOMAIN_SEPARATOR = keccak256(2 abi.encode(3 keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),4 keccak256(bytes(name)),5 keccak256(bytes('1')),6 chainId,7 address(this)8 )9 );10 }सर्व दाखवाEIP-712 साठी डोमेन सेपरेटरची (opens in a new tab) गणना करा.
1 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) प्राप्त करते.
1 require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');डेडलाइननंतर ट्रान्झॅक्शन्स स्वीकारू नका.
1 bytes32 digest = keccak256(2 abi.encodePacked(3 '\x19\x01',4 DOMAIN_SEPARATOR,5 keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))6 )7 );abi.encodePacked(...) हा आम्हाला मिळण्याची अपेक्षा असलेला मेसेज आहे. आम्हाला माहित आहे की नॉन्स काय असावा, त्यामुळे आम्हाला तो पॅरामीटर म्हणून मिळवण्याची गरज नाही.
Ethereum स्वाक्षरी अल्गोरिदमला स्वाक्षरी करण्यासाठी 256 बिट्स मिळण्याची अपेक्षा असते, त्यामुळे आम्ही keccak256 हॅश फंक्शन वापरतो.
1 address recoveredAddress = ecrecover(digest, v, r, s);डायजेस्ट आणि स्वाक्षरीवरून आम्ही ecrecover (opens in a new tab) वापरून ज्याने स्वाक्षरी केली तो पत्ता मिळवू शकतो.
1 require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');2 _approve(owner, spender, value);3 }4जर सर्व काही ठीक असेल, तर याला ERC-20 अप्रूव्ह (opens in a new tab) म्हणून हाताळा.
पेरिफेरी कॉन्ट्रॅक्ट्स
पेरिफेरी कॉन्ट्रॅक्ट्स हे Uniswap चे API (अॅप्लिकेशन प्रोग्राम इंटरफेस) आहेत. ते बाह्य कॉल्ससाठी उपलब्ध आहेत, मग ते इतर कॉन्ट्रॅक्ट्समधून असोत किंवा डिसेंट्रलाइज्ड अॅप्लिकेशन्समधून. तुम्ही कोर कॉन्ट्रॅक्ट्सना थेट कॉल करू शकता, परंतु ते अधिक गुंतागुंतीचे आहे आणि जर तुम्ही चूक केली तर तुमचे मूल्य गमावले जाऊ शकते. कोर कॉन्ट्रॅक्ट्समध्ये केवळ त्यांची फसवणूक होणार नाही याची खात्री करण्यासाठी चाचण्या असतात, इतर कोणासाठीही सॅनिटी चेक्स (sanity checks) नसतात. ते पेरिफेरीमध्ये असतात जेणेकरून आवश्यकतेनुसार ते अपडेट केले जाऊ शकतील.
UniswapV2Router01.sol
या कॉन्ट्रॅक्टमध्ये (opens in a new tab) समस्या आहेत, आणि ते यापुढे वापरले जाऊ नये (opens in a new tab). सुदैवाने, पेरिफेरी कॉन्ट्रॅक्ट्स स्टेटलेस (stateless) असतात आणि त्यांच्याकडे कोणतीही मालमत्ता नसते, त्यामुळे ते डेप्रिकेट (deprecate) करणे आणि लोकांना त्याऐवजी UniswapV2Router02 वापरण्याचा सल्ला देणे सोपे आहे.
UniswapV2Router02.sol
बहुतांश प्रकरणांमध्ये तुम्ही या कॉन्ट्रॅक्टद्वारे (opens in a new tab) Uniswap वापराल. ते कसे वापरायचे ते तुम्ही येथे (opens in a new tab) पाहू शकता.
1pragma solidity =0.6.6;23import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';4import '@uniswap/lib/contracts/libraries/TransferHelper.sol';56import './interfaces/IUniswapV2Router02.sol';7import './libraries/UniswapV2Library.sol';8import './libraries/SafeMath.sol';9import './interfaces/IERC20.sol';10import './interfaces/IWETH.sol';सर्व दाखवायापैकी बहुतांश गोष्टी आपण यापूर्वी पाहिल्या आहेत किंवा त्या अगदी स्पष्ट आहेत. याला एक अपवाद म्हणजे IWETH.sol. Uniswap v2 कोणत्याही ERC-20 टोकन्सच्या जोडीसाठी एक्सचेंजची परवानगी देते, परंतु इथर (ETH) स्वतः एक ERC-20 टोकन नाही. ते या मानकाच्या आधीचे आहे आणि अद्वितीय यंत्रणेद्वारे ट्रान्सफर केले जाते. ERC-20 टोकन्सना लागू होणाऱ्या कॉन्ट्रॅक्ट्समध्ये ETH चा वापर सक्षम करण्यासाठी लोकांनी रॅप्ड इथर (WETH) (opens in a new tab) कॉन्ट्रॅक्ट तयार केले. तुम्ही या कॉन्ट्रॅक्टला ETH पाठवता आणि ते तुम्हाला समतुल्य प्रमाणात WETH मिंट (mint) करून देते. किंवा तुम्ही WETH बर्न (burn) करू शकता आणि परत ETH मिळवू शकता.
1contract UniswapV2Router02 is IUniswapV2Router02 {2 using SafeMath for uint;34 address public immutable override factory;5 address public immutable override WETH;राउटरला कोणती फॅक्टरी वापरायची आहे हे माहित असणे आवश्यक आहे, आणि ज्या ट्रान्झॅक्शन्ससाठी WETH आवश्यक आहे त्यांच्यासाठी कोणते WETH कॉन्ट्रॅक्ट वापरायचे हे माहित असणे आवश्यक आहे. ही मूल्ये इम्युटेबल (immutable) (opens in a new tab) आहेत, याचा अर्थ ती फक्त कन्स्ट्रक्टरमध्ये (constructor) सेट केली जाऊ शकतात. यामुळे युजर्सना असा विश्वास मिळतो की कोणीही त्यांना कमी प्रामाणिक कॉन्ट्रॅक्ट्सकडे निर्देशित करण्यासाठी बदलू शकणार नाही.
1 modifier ensure(uint deadline) {2 require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');3 _;4 }हा मॉडिफायर (modifier) हे सुनिश्चित करतो की वेळेची मर्यादा असलेली ट्रान्झॅक्शन्स ("शक्य असल्यास Y वेळेपूर्वी X करा") त्यांच्या वेळेच्या मर्यादेनंतर होणार नाहीत.
1 constructor(address _factory, address _WETH) public {2 factory = _factory;3 WETH = _WETH;4 }कन्स्ट्रक्टर फक्त इम्युटेबल स्टेट व्हेरिएबल्स सेट करतो.
1 receive() external payable {2 assert(msg.sender == WETH); // फक्त WETH कॉन्ट्रॅक्टमधून फॉलबॅकद्वारे ETH स्वीकारा3 }जेव्हा आपण WETH कॉन्ट्रॅक्टमधून टोकन्स परत ETH मध्ये रिडीम (redeem) करतो तेव्हा हे फंक्शन कॉल केले जाते. आपण वापरत असलेल्या फक्त WETH कॉन्ट्रॅक्टलाच तसे करण्याचा अधिकार आहे.
लिक्विडिटी जोडा
ही फंक्शन्स पेअर एक्सचेंजमध्ये टोकन्स जोडतात, ज्यामुळे लिक्विडिटी पूल वाढतो.
12 // **** लिक्विडिटी जोडा ****3 function _addLiquidity(हे फंक्शन पेअर एक्सचेंजमध्ये जमा केल्या जाणाऱ्या A आणि B टोकन्सच्या रकमेची गणना करण्यासाठी वापरले जाते.
1 address tokenA,2 address tokenB,हे ERC-20 टोकन कॉन्ट्रॅक्ट्सचे पत्ते (addresses) आहेत.
1 uint amountADesired,2 uint amountBDesired,या त्या रकमा आहेत ज्या लिक्विडिटी प्रोव्हायडरला जमा करायच्या आहेत. त्या जमा केल्या जाणाऱ्या A आणि B च्या कमाल रकमा देखील आहेत.
1 uint amountAMin,2 uint amountBMinया जमा करण्यासाठी किमान स्वीकार्य रकमा आहेत. जर ट्रान्झॅक्शन या रकमांसह किंवा त्याहून अधिक रकमेसह होऊ शकत नसेल, तर ते रिव्हर्ट (revert) करा. जर तुम्हाला हे वैशिष्ट्य नको असेल, तर फक्त शून्य (0) निर्दिष्ट करा.
लिक्विडिटी प्रोव्हायडर्स सामान्यतः किमान रक्कम निर्दिष्ट करतात, कारण त्यांना ट्रान्झॅक्शन सध्याच्या एक्सचेंज रेटच्या जवळ असलेल्या एक्सचेंज रेटपर्यंत मर्यादित ठेवायचे असते. जर एक्सचेंज रेटमध्ये खूप चढ-उतार होत असेल तर याचा अर्थ असा असू शकतो की काही बातम्यांमुळे मूळ मूल्ये बदलत आहेत, आणि त्यांना मॅन्युअली काय करायचे ते ठरवायचे असते.
उदाहरणार्थ, अशी कल्पना करा की एक्सचेंज रेट एकास एक (one to one) आहे आणि लिक्विडिटी प्रोव्हायडर ही मूल्ये निर्दिष्ट करतो:
| Parameter | Value |
|---|---|
| amountADesired | 1000 |
| amountBDesired | 1000 |
| amountAMin | 900 |
| amountBMin | 800 |
जोपर्यंत एक्सचेंज रेट 0.9 आणि 1.25 च्या दरम्यान राहतो, तोपर्यंत ट्रान्झॅक्शन होते. जर एक्सचेंज रेट त्या कक्षेच्या बाहेर गेला, तर ट्रान्झॅक्शन रद्द होते.
या खबरदारीचे कारण असे आहे की ट्रान्झॅक्शन्स त्वरित होत नाहीत, तुम्ही ती सबमिट करता आणि शेवटी एक व्हॅलिडेटर (validator) त्यांना ब्लॉकमध्ये समाविष्ट करणार असतो (जोपर्यंत तुमची गॅस प्राईस खूप कमी नसेल, अशा परिस्थितीत तुम्हाला ते ओव्हरराईट करण्यासाठी समान नॉन्स (nonce) आणि उच्च गॅस प्राईससह दुसरे ट्रान्झॅक्शन सबमिट करावे लागेल). सबमिशन आणि समावेशाच्या दरम्यानच्या काळात काय होते यावर तुमचे नियंत्रण नसते.
1 ) internal virtual returns (uint amountA, uint amountB) {हे फंक्शन रिझर्व्ह्समधील सध्याच्या गुणोत्तराइतके गुणोत्तर मिळवण्यासाठी लिक्विडिटी प्रोव्हायडरने जमा करावयाच्या रकमा परत करते.
1 // जर पेअर (जोडी) अद्याप अस्तित्वात नसेल तर ती तयार करा2 if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {3 IUniswapV2Factory(factory).createPair(tokenA, tokenB);4 }जर या टोकन जोडीसाठी अद्याप कोणतेही एक्सचेंज नसेल, तर ते तयार करा.
1 (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);जोडीमधील सध्याचे रिझर्व्ह्स मिळवा.
1 if (reserveA == 0 && reserveB == 0) {2 (amountA, amountB) = (amountADesired, amountBDesired);जर सध्याचे रिझर्व्ह्स रिकामे असतील तर हे एक नवीन पेअर एक्सचेंज आहे. जमा करावयाच्या रकमा लिक्विडिटी प्रोव्हायडरला द्यावयाच्या रकमांसारख्याच असाव्यात.
1 } else {2 uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);जर आपल्याला रकमा काय असतील हे पाहायचे असेल, तर आपण या फंक्शनचा (opens in a new tab) वापर करून इष्टतम (optimal) रक्कम मिळवतो. आपल्याला सध्याच्या रिझर्व्ह्ससारखेच गुणोत्तर हवे आहे.
1 if (amountBOptimal <= amountBDesired) {2 require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');3 (amountA, amountB) = (amountADesired, amountBOptimal);जर amountBOptimal लिक्विडिटी प्रोव्हायडरला जमा करावयाच्या रकमेपेक्षा लहान असेल तर याचा अर्थ असा की टोकन B सध्या लिक्विडिटी जमा करणाऱ्याच्या विचारापेक्षा अधिक मौल्यवान आहे, त्यामुळे कमी रकमेची आवश्यकता आहे.
1 } else {2 uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);3 assert(amountAOptimal <= amountADesired);4 require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');5 (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 टोकन्स.
तुम्ही थेट कोर कॉन्ट्रॅक्टमध्ये लिक्विडिटी जमा करू शकता (UniswapV2Pair::mint (opens in a new tab) वापरून), परंतु कोर कॉन्ट्रॅक्ट फक्त स्वतःची फसवणूक होत नाही ना हे तपासते, त्यामुळे तुम्ही तुमचे ट्रान्झॅक्शन सबमिट करता आणि ते कार्यान्वित होते या दरम्यानच्या वेळेत एक्सचेंज रेट बदलल्यास तुमचे मूल्य गमावण्याचा धोका असतो. जर तुम्ही पेरिफेरी कॉन्ट्रॅक्ट वापरत असाल, तर ते तुम्हाला किती रक्कम जमा करावी लागेल हे शोधून काढते आणि ती त्वरित जमा करते, त्यामुळे एक्सचेंज रेट बदलत नाही आणि तुमचे कोणतेही नुकसान होत नाही.
1 function addLiquidity(2 address tokenA,3 address tokenB,4 uint amountADesired,5 uint amountBDesired,6 uint amountAMin,7 uint amountBMin,8 address to,9 uint deadlineसर्व दाखवालिक्विडिटी जमा करण्यासाठी ट्रान्झॅक्शनद्वारे हे फंक्शन कॉल केले जाऊ शकते. दोन अपवाद वगळता बहुतांश पॅरामीटर्स वरील _addLiquidity प्रमाणेच आहेत:
. to हा तो पत्ता आहे ज्याला पूलमधील लिक्विडिटी प्रोव्हायडरचा हिस्सा दर्शविण्यासाठी नवीन लिक्विडिटी टोकन्स मिंट करून मिळतात
. deadline ही ट्रान्झॅक्शनवरील वेळेची मर्यादा आहे
1 ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {2 (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);3 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);आपण प्रत्यक्षात जमा करावयाच्या रकमांची गणना करतो आणि नंतर लिक्विडिटी पूलचा पत्ता शोधतो. गॅस वाचवण्यासाठी आपण हे फॅक्टरीला विचारून करत नाही, तर pairFor हे लायब्ररी फंक्शन वापरून करतो (खाली लायब्ररीमध्ये पहा)
1 TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);2 TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);युजरकडून पेअर एक्सचेंजमध्ये टोकन्सच्या योग्य रकमा ट्रान्सफर करा.
1 liquidity = IUniswapV2Pair(pair).mint(to);2 }त्या बदल्यात पूलच्या आंशिक मालकीसाठी to पत्त्याला लिक्विडिटी टोकन्स द्या. कोर कॉन्ट्रॅक्टचे mint फंक्शन पाहते की त्याच्याकडे किती अतिरिक्त टोकन्स आहेत (गेल्या वेळी लिक्विडिटी बदलली तेव्हा त्याच्याकडे जे होते त्याच्या तुलनेत) आणि त्यानुसार लिक्विडिटी मिंट करते.
1 function addLiquidityETH(2 address token,3 uint amountTokenDesired,जेव्हा लिक्विडिटी प्रोव्हायडरला Token/ETH पेअर एक्सचेंजला लिक्विडिटी प्रदान करायची असते, तेव्हा काही फरक असतात. कॉन्ट्रॅक्ट लिक्विडिटी प्रोव्हायडरसाठी ETH रॅप (wrap) करण्याचे काम हाताळते. युजरला किती ETH जमा करायचे आहेत हे निर्दिष्ट करण्याची आवश्यकता नाही, कारण युजर फक्त त्यांना ट्रान्झॅक्शनसोबत पाठवतो (ही रक्कम msg.value मध्ये उपलब्ध असते).
1 uint amountTokenMin,2 uint amountETHMin,3 address to,4 uint deadline5 ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {6 (amountToken, amountETH) = _addLiquidity(7 token,8 WETH,9 amountTokenDesired,10 msg.value,11 amountTokenMin,12 amountETHMin13 );14 address pair = UniswapV2Library.pairFor(factory, token, WETH);15 TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);16 IWETH(WETH).deposit{value: amountETH}();17 assert(IWETH(WETH).transfer(pair, amountETH));सर्व दाखवाETH जमा करण्यासाठी कॉन्ट्रॅक्ट प्रथम त्याला WETH मध्ये रॅप करते आणि नंतर WETH ला जोडीमध्ये ट्रान्सफर करते. लक्षात घ्या की ट्रान्सफर एका assert मध्ये रॅप केलेले आहे. याचा अर्थ असा की जर ट्रान्सफर अयशस्वी झाले तर हा कॉन्ट्रॅक्ट कॉल देखील अयशस्वी होतो, आणि त्यामुळे रॅपिंग खरोखर होत नाही.
1 liquidity = IUniswapV2Pair(pair).mint(to);2 // काही उरलेले (dust) eth असल्यास, रिफंड करा3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);4 }युजरने आपल्याला आधीच ETH पाठवले आहेत, त्यामुळे जर काही अतिरिक्त शिल्लक राहिले असेल (कारण दुसरे टोकन युजरच्या विचारापेक्षा कमी मौल्यवान आहे), तर आपल्याला रिफंड (refund) जारी करणे आवश्यक आहे.
लिक्विडिटी काढा
ही फंक्शन्स लिक्विडिटी काढून टाकतील आणि लिक्विडिटी प्रोव्हायडरला परतफेड करतील.
1 // **** लिक्विडिटी काढा ****2 function removeLiquidity(3 address tokenA,4 address tokenB,5 uint liquidity,6 uint amountAMin,7 uint amountBMin,8 address to,9 uint deadline10 ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {सर्व दाखवालिक्विडिटी काढण्याचे सर्वात सोपे प्रकरण. लिक्विडिटी प्रोव्हायडर स्वीकारण्यास सहमत असलेल्या प्रत्येक टोकनची किमान रक्कम असते, आणि ते डेडलाईनपूर्वी (deadline) होणे आवश्यक आहे.
1 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);2 IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // पेअरला लिक्विडिटी पाठवा3 (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);कोर कॉन्ट्रॅक्टचे burn फंक्शन युजरला टोकन्स परत देण्याचे काम हाताळते.
1 (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);जेव्हा एखादे फंक्शन अनेक मूल्ये परत करते, परंतु आपल्याला त्यापैकी काहींमध्येच स्वारस्य असते, तेव्हा आपण फक्त तीच मूल्ये अशा प्रकारे मिळवतो. एखादे मूल्य वाचणे आणि ते कधीही न वापरणे यापेक्षा गॅसच्या दृष्टीने हे काहीसे स्वस्त आहे.
1 (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);कोर कॉन्ट्रॅक्ट ज्या प्रकारे रकमा परत करते (लोअर अॅड्रेस टोकन प्रथम) त्यावरून युजरला ज्या प्रकारे त्या अपेक्षित आहेत ( tokenA आणि tokenB शी संबंधित) त्यामध्ये रकमांचे भाषांतर करा.
1 require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');2 require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');3 }प्रथम ट्रान्सफर करणे आणि नंतर ते कायदेशीर आहे की नाही हे पडताळून पाहणे ठीक आहे, कारण जर ते नसेल तर आपण सर्व स्टेट बदलांमधून रिव्हर्ट करू.
1 function removeLiquidityETH(2 address token,3 uint liquidity,4 uint amountTokenMin,5 uint amountETHMin,6 address to,7 uint deadline8 ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {9 (amountToken, amountETH) = removeLiquidity(10 token,11 WETH,12 liquidity,13 amountTokenMin,14 amountETHMin,15 address(this),16 deadline17 );18 TransferHelper.safeTransfer(token, to, amountToken);19 IWETH(WETH).withdraw(amountETH);20 TransferHelper.safeTransferETH(to, amountETH);21 }सर्व दाखवाETH साठी लिक्विडिटी काढणे जवळजवळ सारखेच आहे, फक्त एवढाच फरक आहे की आपल्याला WETH टोकन्स मिळतात आणि नंतर लिक्विडिटी प्रोव्हायडरला परत देण्यासाठी आपण त्यांना ETH साठी रिडीम करतो.
1 function removeLiquidityWithPermit(2 address tokenA,3 address tokenB,4 uint liquidity,5 uint amountAMin,6 uint amountBMin,7 address to,8 uint deadline,9 bool approveMax, uint8 v, bytes32 r, bytes32 s10 ) external virtual override returns (uint amountA, uint amountB) {11 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);12 uint value = approveMax ? uint(-1) : liquidity;13 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);14 (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);15 }161718 function removeLiquidityETHWithPermit(19 address token,20 uint liquidity,21 uint amountTokenMin,22 uint amountETHMin,23 address to,24 uint deadline,25 bool approveMax, uint8 v, bytes32 r, bytes32 s26 ) external virtual override returns (uint amountToken, uint amountETH) {27 address pair = UniswapV2Library.pairFor(factory, token, WETH);28 uint value = approveMax ? uint(-1) : liquidity;29 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);30 (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);31 }सर्व दाखवाही फंक्शन्स इथर नसलेल्या युजर्सना पूलमधून पैसे काढण्याची परवानगी देण्यासाठी मेटा-ट्रान्झॅक्शन्स रिले (relay) करतात, यासाठी परमिट मेकॅनिझम (permit mechanism) वापरले जाते.
12 // **** लिक्विडिटी काढा (फी-ऑन-ट्रान्सफर टोकन्सना सपोर्ट करणारे) ****3 function removeLiquidityETHSupportingFeeOnTransferTokens(4 address token,5 uint liquidity,6 uint amountTokenMin,7 uint amountETHMin,8 address to,9 uint deadline10 ) public virtual override ensure(deadline) returns (uint amountETH) {11 (, amountETH) = removeLiquidity(12 token,13 WETH,14 liquidity,15 amountTokenMin,16 amountETHMin,17 address(this),18 deadline19 );20 TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));21 IWETH(WETH).withdraw(amountETH);22 TransferHelper.safeTransferETH(to, amountETH);23 }24सर्व दाखवाहे फंक्शन ट्रान्सफर किंवा स्टोरेज फी असलेल्या टोकन्ससाठी वापरले जाऊ शकते. जेव्हा एखाद्या टोकनला अशी फी असते तेव्हा आपल्याला किती टोकन परत मिळतील हे सांगण्यासाठी आपण removeLiquidity फंक्शनवर अवलंबून राहू शकत नाही, त्यामुळे आपल्याला प्रथम पैसे काढावे लागतील आणि नंतर बॅलन्स मिळवावा लागेल.
123 function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(4 address token,5 uint liquidity,6 uint amountTokenMin,7 uint amountETHMin,8 address to,9 uint deadline,10 bool approveMax, uint8 v, bytes32 r, bytes32 s11 ) external virtual override returns (uint amountETH) {12 address pair = UniswapV2Library.pairFor(factory, token, WETH);13 uint value = approveMax ? uint(-1) : liquidity;14 IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);15 amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(16 token, liquidity, amountTokenMin, amountETHMin, to, deadline17 );18 }सर्व दाखवाअंतिम फंक्शन स्टोरेज फीला मेटा-ट्रान्झॅक्शन्ससोबत जोडते.
ट्रेड
1 // **** स्वॅप (SWAP) ****2 // सुरुवातीची रक्कम पहिल्या पेअरला आधीच पाठवली असणे आवश्यक आहे3 function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {हे फंक्शन ट्रेडर्ससाठी उघड केलेल्या फंक्शन्ससाठी आवश्यक असलेली अंतर्गत प्रक्रिया करते.
1 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). त्याऐवजी, स्वॅप (swap) फंक्शन्स पाथच्या (path) संकल्पनेला समर्थन देतात. एक ट्रेडर A ला B साठी, B ला C साठी, आणि C ला D साठी एक्सचेंज करू शकतो, त्यामुळे थेट A-D पेअर एक्सचेंजची आवश्यकता नाही.
या मार्केट्सवरील किमती सिंक्रोनाइझ (synchronized) असतात, कारण जेव्हा त्या सिंकच्या बाहेर असतात तेव्हा ते आर्बिट्रेजसाठी (arbitrage) संधी निर्माण करते. उदाहरणार्थ, A, B, आणि C या तीन टोकन्सची कल्पना करा. तीन पेअर एक्सचेंजेस आहेत, प्रत्येक जोडीसाठी एक.
- सुरुवातीची परिस्थिती
- एक ट्रेडर 24.695 A टोकन्स विकतो आणि 25.305 B टोकन्स मिळवतो.
- ट्रेडर 25.305 C टोकन्ससाठी 24.695 B टोकन्स विकतो, आणि अंदाजे 0.61 B टोकन्स नफा म्हणून ठेवतो.
- त्यानंतर ट्रेडर 25.305 A टोकन्ससाठी 24.695 C टोकन्स विकतो, आणि अंदाजे 0.61 C टोकन्स नफा म्हणून ठेवतो. ट्रेडरकडे 0.61 अतिरिक्त A टोकन्स देखील आहेत (ट्रेडरकडे शेवटी असलेले 25.305, वजा 24.695 ची मूळ गुंतवणूक).
| Step | A-B Exchange | B-C Exchange | A-C Exchange |
|---|---|---|---|
| 1 | A:1000 B:1050 A/B=1.05 | B:1000 C:1050 B/C=1.05 | A:1050 C:1000 C/A=1.05 |
| 2 | A:1024.695 B:1024.695 A/B=1 | B:1000 C:1050 B/C=1.05 | A:1050 C:1000 C/A=1.05 |
| 3 | A:1024.695 B:1024.695 A/B=1 | B:1024.695 C:1024.695 B/C=1 | A:1050 C:1000 C/A=1.05 |
| 4 | A:1024.695 B:1024.695 A/B=1 | B:1024.695 C:1024.695 B/C=1 | A:1024.695 C:1024.695 C/A=1 |
1 (address input, address output) = (path[i], path[i + 1]);2 (address token0,) = UniswapV2Library.sortTokens(input, output);3 uint amountOut = amounts[i + 1];आपण सध्या हाताळत असलेली जोडी मिळवा, ती सॉर्ट (sort) करा (जोडीसोबत वापरण्यासाठी) आणि अपेक्षित आउटपुट रक्कम मिळवा.
1 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));पेअर एक्सचेंजला ज्या प्रकारे अपेक्षित आहे त्या प्रकारे सॉर्ट केलेल्या अपेक्षित आउट रकमा मिळवा.
1 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;हे शेवटचे एक्सचेंज आहे का? तसे असल्यास, ट्रेडसाठी मिळालेले टोकन्स डेस्टिनेशनला (destination) पाठवा. नसल्यास, ते पुढील पेअर एक्सचेंजला पाठवा.
12 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(3 amount0Out, amount1Out, to, new bytes(0)4 );5 }6 }टोकन्स स्वॅप करण्यासाठी प्रत्यक्षात पेअर एक्सचेंजला कॉल करा. एक्सचेंजविषयी सांगण्यासाठी आपल्याला कॉलबॅकची (callback) आवश्यकता नाही, त्यामुळे आपण त्या फील्डमध्ये कोणतेही बाइट्स (bytes) पाठवत नाही.
1 function swapExactTokensForTokens(हे फंक्शन ट्रेडर्सद्वारे एका टोकनला दुसऱ्या टोकनसाठी स्वॅप करण्यासाठी थेट वापरले जाते.
1 uint amountIn,2 uint amountOutMin,3 address[] calldata path,या पॅरामीटरमध्ये ERC-20 कॉन्ट्रॅक्ट्सचे पत्ते असतात. वर स्पष्ट केल्याप्रमाणे, हा एक अॅरे (array) आहे कारण तुमच्याकडे असलेल्या मालमत्तेवरून तुम्हाला हव्या असलेल्या मालमत्तेपर्यंत पोहोचण्यासाठी तुम्हाला अनेक पेअर एक्सचेंजेसमधून जावे लागू शकते.
Solidity मधील फंक्शन पॅरामीटर एकतर memory किंवा calldata मध्ये स्टोअर केले जाऊ शकते. जर फंक्शन कॉन्ट्रॅक्टचा एंट्री पॉईंट असेल, जे थेट युजरकडून (ट्रान्झॅक्शन वापरून) किंवा वेगळ्या कॉन्ट्रॅक्टमधून कॉल केले गेले असेल, तर पॅरामीटरचे मूल्य थेट कॉल डेटामधून घेतले जाऊ शकते. जर फंक्शन अंतर्गत कॉल केले गेले असेल, जसे की वरील _swap, तर पॅरामीटर्स memory मध्ये स्टोअर करावे लागतात. कॉल केलेल्या कॉन्ट्रॅक्टच्या दृष्टिकोनातून calldata हे रीड ओन्ली (read only) असते.
uint किंवा address सारख्या स्केलर (scalar) प्रकारांसह कंपायलर (compiler) आपल्यासाठी स्टोरेजची निवड हाताळतो, परंतु अॅरेसह, जे मोठे आणि अधिक महाग असतात, आपण वापरल्या जाणाऱ्या स्टोरेजचा प्रकार निर्दिष्ट करतो.
1 address to,2 uint deadline3 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {रिटर्न व्हॅल्यूज (Return values) नेहमी मेमरीमध्ये परत केल्या जातात.
1 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);2 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');प्रत्येक स्वॅपमध्ये खरेदी करावयाच्या रकमेची गणना करा. जर परिणाम ट्रेडर स्वीकारण्यास तयार असलेल्या किमान रकमेपेक्षा कमी असेल, तर ट्रान्झॅक्शनमधून रिव्हर्ट करा.
1 TransferHelper.safeTransferFrom(2 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]3 );4 _swap(amounts, path, to);5 }शेवटी, सुरुवातीचे ERC-20 टोकन पहिल्या पेअर एक्सचेंजसाठी खात्यात ट्रान्सफर करा आणि _swap ला कॉल करा. हे सर्व एकाच ट्रान्झॅक्शनमध्ये होत आहे, त्यामुळे पेअर एक्सचेंजला माहित असते की कोणतेही अनपेक्षित टोकन्स या ट्रान्सफरचा भाग आहेत.
1 function swapTokensForExactTokens(2 uint amountOut,3 uint amountInMax,4 address[] calldata path,5 address to,6 uint deadline7 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {8 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);9 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');10 TransferHelper.safeTransferFrom(11 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]12 );13 _swap(amounts, path, to);14 }सर्व दाखवामागील फंक्शन, swapTokensForTokens, ट्रेडरला तो देऊ इच्छित असलेल्या इनपुट टोकन्सची अचूक संख्या आणि त्या बदल्यात तो प्राप्त करू इच्छित असलेल्या आउटपुट टोकन्सची किमान संख्या निर्दिष्ट करण्याची परवानगी देते. हे फंक्शन रिव्हर्स स्वॅप (reverse swap) करते, ते ट्रेडरला त्याला हव्या असलेल्या आउटपुट टोकन्सची संख्या आणि त्यासाठी तो देऊ इच्छित असलेल्या इनपुट टोकन्सची कमाल संख्या निर्दिष्ट करू देते.
दोन्ही प्रकरणांमध्ये, ट्रेडरला प्रथम या पेरिफेरी कॉन्ट्रॅक्टला त्यांना ट्रान्सफर करण्याची परवानगी देण्यासाठी अलाउन्स (allowance) द्यावा लागतो.
1 function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)2 external3 virtual4 override5 payable6 ensure(deadline)7 returns (uint[] memory amounts)8 {9 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');10 amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);11 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');12 IWETH(WETH).deposit{value: amounts[0]}();13 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));14 _swap(amounts, path, to);15 }161718 function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)19 external20 virtual21 override22 ensure(deadline)23 returns (uint[] memory amounts)24 {25 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');26 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);27 require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');28 TransferHelper.safeTransferFrom(29 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]30 );31 _swap(amounts, path, address(this));32 IWETH(WETH).withdraw(amounts[amounts.length - 1]);33 TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);34 }35363738 function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)39 external40 virtual41 override42 ensure(deadline)43 returns (uint[] memory amounts)44 {45 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');46 amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);47 require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');48 TransferHelper.safeTransferFrom(49 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]50 );51 _swap(amounts, path, address(this));52 IWETH(WETH).withdraw(amounts[amounts.length - 1]);53 TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);54 }555657 function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)58 external59 virtual60 override61 payable62 ensure(deadline)63 returns (uint[] memory amounts)64 {65 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');66 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);67 require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');68 IWETH(WETH).deposit{value: amounts[0]}();69 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));70 _swap(amounts, path, to);71 // काही उरलेले (dust) eth असल्यास, रिफंड करा72 if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);73 }सर्व दाखवाया चारही व्हेरिएंट्समध्ये (variants) ETH आणि टोकन्स दरम्यान ट्रेडिंगचा समावेश आहे. फरक एवढाच आहे की आपण एकतर ट्रेडरकडून ETH प्राप्त करतो आणि WETH मिंट करण्यासाठी त्याचा वापर करतो, किंवा आपण पाथमधील शेवटच्या एक्सचेंजकडून WETH प्राप्त करतो आणि ते बर्न करतो, आणि परिणामी मिळणारे ETH ट्रेडरला परत पाठवतो.
1 // **** स्वॅप (फी-ऑन-ट्रान्सफर टोकन्सना सपोर्ट करणारे) ****2 // सुरुवातीची रक्कम पहिल्या पेअरला आधीच पाठवली असणे आवश्यक आहे3 function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {ट्रान्सफर किंवा स्टोरेज फी असलेल्या टोकन्सना स्वॅप करण्यासाठी हे अंतर्गत फंक्शन आहे जेणेकरून (ही समस्या (opens in a new tab)) सोडवता येईल.
1 for (uint i; i < path.length - 1; i++) {2 (address input, address output) = (path[i], path[i + 1]);3 (address token0,) = UniswapV2Library.sortTokens(input, output);4 IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));5 uint amountInput;6 uint amountOutput;7 { // 'stack too deep' एरर्स टाळण्यासाठी स्कोप8 (uint reserve0, uint reserve1,) = pair.getReserves();9 (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);10 amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);11 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);सर्व दाखवाट्रान्सफर फीमुळे आपण प्रत्येक ट्रान्सफरमधून आपल्याला किती मिळते हे सांगण्यासाठी getAmountsOut फंक्शनवर अवलंबून राहू शकत नाही (जसे आपण मूळ _swap ला कॉल करण्यापूर्वी करतो). त्याऐवजी आपल्याला प्रथम ट्रान्सफर करावे लागेल आणि नंतर आपल्याला किती टोकन्स परत मिळाले हे पहावे लागेल.
टीप: सिद्धांतानुसार आपण _swap ऐवजी फक्त हे फंक्शन वापरू शकतो, परंतु काही प्रकरणांमध्ये (उदाहरणार्थ, जर आवश्यक किमान रक्कम पूर्ण करण्यासाठी शेवटी पुरेशी रक्कम नसल्यामुळे ट्रान्सफर रिव्हर्ट झाले) तर त्यासाठी अधिक गॅस खर्च होईल. ट्रान्सफर फी टोकन्स खूप दुर्मिळ आहेत, त्यामुळे आपल्याला त्यांना सामावून घेण्याची आवश्यकता असली तरी, सर्व स्वॅप्स किमान त्यापैकी एकामधून जातात असे गृहीत धरण्याची आवश्यकता नाही.
1 }2 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));3 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;4 pair.swap(amount0Out, amount1Out, to, new bytes(0));5 }6 }789 function swapExactTokensForTokensSupportingFeeOnTransferTokens(10 uint amountIn,11 uint amountOutMin,12 address[] calldata path,13 address to,14 uint deadline15 ) external virtual override ensure(deadline) {16 TransferHelper.safeTransferFrom(17 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn18 );19 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);20 _swapSupportingFeeOnTransferTokens(path, to);21 require(22 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,23 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'24 );25 }262728 function swapExactETHForTokensSupportingFeeOnTransferTokens(29 uint amountOutMin,30 address[] calldata path,31 address to,32 uint deadline33 )34 external35 virtual36 override37 payable38 ensure(deadline)39 {40 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');41 uint amountIn = msg.value;42 IWETH(WETH).deposit{value: amountIn}();43 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));44 uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);45 _swapSupportingFeeOnTransferTokens(path, to);46 require(47 IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,48 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'49 );50 }515253 function swapExactTokensForETHSupportingFeeOnTransferTokens(54 uint amountIn,55 uint amountOutMin,56 address[] calldata path,57 address to,58 uint deadline59 )60 external61 virtual62 override63 ensure(deadline)64 {65 require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');66 TransferHelper.safeTransferFrom(67 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn68 );69 _swapSupportingFeeOnTransferTokens(path, address(this));70 uint amountOut = IERC20(WETH).balanceOf(address(this));71 require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');72 IWETH(WETH).withdraw(amountOut);73 TransferHelper.safeTransferETH(to, amountOut);74 }सर्व दाखवाहे सामान्य टोकन्ससाठी वापरले जाणारे तेच व्हेरिएंट्स आहेत, परंतु ते त्याऐवजी _swapSupportingFeeOnTransferTokens ला कॉल करतात.
1 // **** लायब्ररी फंक्शन्स ****2 function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {3 return UniswapV2Library.quote(amountA, reserveA, reserveB);4 }56 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)7 public8 pure9 virtual10 override11 returns (uint amountOut)12 {13 return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);14 }1516 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)17 public18 pure19 virtual20 override21 returns (uint amountIn)22 {23 return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);24 }2526 function getAmountsOut(uint amountIn, address[] memory path)27 public28 view29 virtual30 override31 returns (uint[] memory amounts)32 {33 return UniswapV2Library.getAmountsOut(factory, amountIn, path);34 }3536 function getAmountsIn(uint amountOut, address[] memory path)37 public38 view39 virtual40 override41 returns (uint[] memory amounts)42 {43 return UniswapV2Library.getAmountsIn(factory, amountOut, path);44 }45}सर्व दाखवाही फंक्शन्स फक्त प्रॉक्सी (proxies) आहेत जी UniswapV2Library फंक्शन्सना कॉल करतात.
UniswapV2Migrator.sol
हे कॉन्ट्रॅक्ट जुन्या v1 वरून v2 वर एक्सचेंजेस मायग्रेट (migrate) करण्यासाठी वापरले गेले होते. आता ते मायग्रेट झाले असल्याने, ते यापुढे संबंधित नाही.
लायब्ररीज
SafeMath library (opens in a new tab) चे दस्तऐवजीकरण चांगले केले आहे, त्यामुळे येथे त्याचे दस्तऐवजीकरण करण्याची आवश्यकता नाही.
Math
या लायब्ररीमध्ये काही गणितीय फंक्शन्स आहेत ज्यांची सामान्यतः Solidity कोडमध्ये आवश्यकता नसते, त्यामुळे ते भाषेचा भाग नाहीत.
1pragma solidity =0.5.16;23// विविध गणितीय ऑपरेशन्स करण्यासाठी एक लायब्ररी45library Math {6 function min(uint x, uint y) internal pure returns (uint z) {7 z = x < y ? x : y;8 }910 // बॅबिलोनियन पद्धत (https://wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)11 function sqrt(uint y) internal pure returns (uint z) {12 if (y > 3) {13 z = y;14 uint x = y / 2 + 1;सर्व दाखवाx ने सुरुवात करा जो वर्गमुळापेक्षा जास्त असलेला अंदाज आहे (याच कारणामुळे आपल्याला 1-3 ला विशेष प्रकरणे म्हणून हाताळण्याची आवश्यकता आहे).
1 while (x < z) {2 z = x;3 x = (y / x + x) / 2;अधिक जवळचा अंदाज मिळवा, मागील अंदाजाची सरासरी आणि ज्या संख्येचे वर्गमूळ आपण शोधण्याचा प्रयत्न करत आहोत तिला मागील अंदाजाने भागून मिळणारी संख्या. जोपर्यंत नवीन अंदाज विद्यमान अंदाजापेक्षा कमी होत नाही तोपर्यंत पुनरावृत्ती करा. अधिक तपशीलांसाठी, येथे पहा (opens in a new tab).
1 }2 } else if (y != 0) {3 z = 1;आपल्याला शून्याच्या वर्गमुळाची कधीही आवश्यकता भासू नये. एक, दोन आणि तीनचे वर्गमूळ साधारणपणे एक असते (आपण पूर्णांक वापरतो, त्यामुळे आपण अपूर्णांकाकडे दुर्लक्ष करतो).
1 }2 }3}फिक्स्ड पॉइंट फ्रॅक्शन्स (UQ112x112)
ही लायब्ररी अपूर्णांक हाताळते, जे सामान्यतः Ethereum अंकगणिताचा भाग नसतात. हे x या संख्येला x*2^112 म्हणून एन्कोड करून असे करते. यामुळे आपल्याला मूळ बेरीज आणि वजाबाकी opcodes मध्ये कोणताही बदल न करता वापरता येतात.
1pragma solidity =0.5.16;23// बायनरी फिक्स्ड पॉईंट नंबर्स हाताळण्यासाठी एक लायब्ररी (https://wikipedia.org/wiki/Q_(number_format))45// रेंज: [0, 2**112 - 1]6// रिझोल्यूशन: 1 / 2**11278library UQ112x112 {9 uint224 constant Q112 = 2**112;सर्व दाखवाQ112 हे एकासाठी एन्कोडिंग आहे.
1 // uint112 ला UQ112x112 म्हणून एन्कोड करा2 function encode(uint112 y) internal pure returns (uint224 z) {3 z = uint224(y) * Q112; // कधीही ओव्हरफ्लो होत नाही4 }कारण y हे uint112 आहे, ते जास्तीत जास्त 2^112-1 असू शकते. ती संख्या अजूनही UQ112x112 म्हणून एन्कोड केली जाऊ शकते.
1 // UQ112x112 ला uint112 ने भागा, आणि UQ112x112 रिटर्न करा2 function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {3 z = x / uint224(y);4 }5}जर आपण दोन UQ112x112 मूल्यांना भागले, तर परिणामाला यापुढे 2^112 ने गुणले जात नाही. त्यामुळे त्याऐवजी आपण छेदासाठी एक पूर्णांक घेतो. गुणाकार करण्यासाठी आपल्याला अशीच युक्ती वापरावी लागली असती, परंतु आपल्याला UQ112x112 मूल्यांचा गुणाकार करण्याची आवश्यकता नाही.
UniswapV2Library
ही लायब्ररी फक्त पेरिफेरी कॉन्ट्रॅक्ट्सद्वारे वापरली जाते
1pragma solidity >=0.5.0;23import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';45import "./SafeMath.sol";67library UniswapV2Library {8 using SafeMath for uint;910 // सॉर्ट केलेले टोकन अॅड्रेसेस रिटर्न करते, या क्रमाने सॉर्ट केलेल्या पेअर्समधून रिटर्न व्हॅल्यूज हाताळण्यासाठी वापरले जाते11 function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {12 require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');13 (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);14 require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');15 }सर्व दाखवादोन टोकन्सना पत्त्यानुसार क्रमवारी लावा, जेणेकरून आपल्याला त्यांच्यासाठी पेअर एक्सचेंजचा पत्ता मिळू शकेल. हे आवश्यक आहे कारण अन्यथा आपल्याकडे दोन शक्यता असतील, एक A,B पॅरामीटर्ससाठी आणि दुसरी B,A पॅरामीटर्ससाठी, ज्यामुळे एकाऐवजी दोन एक्सचेंजेस तयार होतील.
1 // कोणतेही बाह्य कॉल्स न करता पेअरसाठी CREATE2 अॅड्रेस कॅल्क्युलेट करते2 function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {3 (address token0, address token1) = sortTokens(tokenA, tokenB);4 pair = address(uint(keccak256(abi.encodePacked(5 hex'ff',6 factory,7 keccak256(abi.encodePacked(token0, token1)),8 hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init कोड हॅश9 ))));10 }सर्व दाखवाहे फंक्शन दोन टोकन्ससाठी पेअर एक्सचेंजच्या पत्त्याची गणना करते. हे कॉन्ट्रॅक्ट CREATE2 opcode (opens in a new tab) वापरून तयार केले आहे, त्यामुळे जर आपल्याला ते वापरत असलेले पॅरामीटर्स माहित असतील तर आपण त्याच अल्गोरिदमचा वापर करून पत्त्याची गणना करू शकतो. फॅक्टरीला विचारण्यापेक्षा हे खूप स्वस्त आहे, आणि
1 // पेअरसाठी रिझर्व्ह फेच करते आणि सॉर्ट करते2 function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {3 (address token0,) = sortTokens(tokenA, tokenB);4 (uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();5 (reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);6 }हे फंक्शन पेअर एक्सचेंजकडे असलेल्या दोन टोकन्सचे रिझर्व्ह परत करते. लक्षात घ्या की ते कोणत्याही क्रमाने टोकन्स प्राप्त करू शकते आणि अंतर्गत वापरासाठी त्यांची क्रमवारी लावते.
1 // एखाद्या अॅसेटची काही रक्कम आणि पेअर रिझर्व्ह दिले असता, दुसऱ्या अॅसेटची समतुल्य रक्कम रिटर्न करते2 function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {3 require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');4 require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');5 amountB = amountA.mul(reserveB) / reserveA;6 }जर कोणतीही फी समाविष्ट नसेल तर हे फंक्शन तुम्हाला टोकन A च्या बदल्यात मिळणाऱ्या टोकन B ची रक्कम देते. ही गणना हे लक्षात घेते की ट्रान्सफरमुळे विनिमय दर बदलतो.
1 // एखाद्या अॅसेटची इनपुट रक्कम आणि पेअर रिझर्व्ह दिले असता, दुसऱ्या अॅसेटची कमाल आउटपुट रक्कम रिटर्न करते2 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {जर पेअर एक्सचेंज वापरण्यासाठी कोणतीही फी नसेल तर वरील quote फंक्शन उत्तम काम करते. तथापि, जर 0.3% एक्सचेंज फी असेल तर तुम्हाला प्रत्यक्षात मिळणारी रक्कम कमी असते. हे फंक्शन एक्सचेंज फी नंतरच्या रकमेची गणना करते.
12 require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');3 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');4 uint amountInWithFee = amountIn.mul(997);5 uint numerator = amountInWithFee.mul(reserveOut);6 uint denominator = reserveIn.mul(1000).add(amountInWithFee);7 amountOut = numerator / denominator;8 }Solidity मूळतः अपूर्णांक हाताळत नाही, त्यामुळे आपण रकमेला फक्त 0.997 ने गुणू शकत नाही. त्याऐवजी, आपण अंशाला 997 ने आणि छेदाला 1000 ने गुणतो, ज्यामुळे तोच परिणाम साध्य होतो.
1 // एखाद्या अॅसेटची आउटपुट रक्कम आणि पेअर रिझर्व्ह दिले असता, दुसऱ्या अॅसेटची आवश्यक इनपुट रक्कम रिटर्न करते2 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {3 require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');4 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');5 uint numerator = reserveIn.mul(amountOut).mul(1000);6 uint denominator = reserveOut.sub(amountOut).mul(997);7 amountIn = (numerator / denominator).add(1);8 }हे फंक्शन साधारणपणे तेच काम करते, परंतु ते आउटपुट रक्कम मिळवते आणि इनपुट प्रदान करते.
12 // कोणत्याही संख्येच्या पेअर्सवर चेन्ड (chained) getAmountOut कॅल्क्युलेशन्स करते3 function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {4 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');5 amounts = new uint[](path.length);6 amounts[0] = amountIn;7 for (uint i; i < path.length - 1; i++) {8 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);9 amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);10 }11 }1213 // कोणत्याही संख्येच्या पेअर्सवर चेन्ड (chained) getAmountIn कॅल्क्युलेशन्स करते14 function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {15 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');16 amounts = new uint[](path.length);17 amounts[amounts.length - 1] = amountOut;18 for (uint i = path.length - 1; i > 0; i--) {19 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);20 amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);21 }22 }23}सर्व दाखवाजेव्हा अनेक पेअर एक्सचेंजेसमधून जाणे आवश्यक असते तेव्हा ही दोन फंक्शन्स मूल्ये ओळखण्याचे काम हाताळतात.
ट्रान्सफर हेल्पर
ही लायब्ररी (opens in a new tab) ERC-20 आणि Ethereum ट्रान्सफर्सच्या आसपास यश तपासणी जोडते जेणेकरून रिव्हर्ट आणि false मूल्य परतावा एकाच प्रकारे हाताळला जाईल.
1// SPDX-License-Identifier: GPL-3.0-or-later23pragma solidity >=0.6.0;45// ERC20 टोकन्सशी संवाद साधण्यासाठी आणि ETH पाठवण्यासाठी हेल्पर मेथड्स जे सातत्याने true/false रिटर्न करत नाहीत6library TransferHelper {7 function safeApprove(8 address token,9 address to,10 uint256 value11 ) internal {12 // bytes4(keccak256(bytes('approve(address,uint256)')));13 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));14सर्व दाखवाआपण दोनपैकी एका मार्गाने वेगळ्या कॉन्ट्रॅक्टला कॉल करू शकतो:
- फंक्शन कॉल तयार करण्यासाठी इंटरफेस व्याख्येचा वापर करा
- कॉल तयार करण्यासाठी "मॅन्युअली" अॅप्लिकेशन बायनरी इंटरफेस (ABI) (opens in a new tab) चा वापर करा. कोडच्या लेखकाने हेच करण्याचे ठरवले आहे.
1 require(2 success && (data.length == 0 || abi.decode(data, (bool))),3 'TransferHelper::safeApprove: approve failed'4 );5 }ERC-20 मानकापूर्वी तयार केलेल्या टोकनसह बॅकवर्ड्स कंपॅटिबिलिटीसाठी, ERC-20 कॉल एकतर रिव्हर्ट करून (ज्या प्रकरणात success हे false असते) किंवा यशस्वी होऊन आणि false मूल्य परत करून (ज्या प्रकरणात आउटपुट डेटा असतो, आणि जर तुम्ही त्याला बुलियन म्हणून डीकोड केले तर तुम्हाला false मिळते) अयशस्वी होऊ शकतो.
123 function safeTransfer(4 address token,5 address to,6 uint256 value7 ) internal {8 // bytes4(keccak256(bytes('transfer(address,uint256)')));9 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));10 require(11 success && (data.length == 0 || abi.decode(data, (bool))),12 'TransferHelper::safeTransfer: transfer failed'13 );14 }सर्व दाखवाहे फंक्शन ERC-20 ची ट्रान्सफर कार्यक्षमता (opens in a new tab) लागू करते, जे एका खात्याला वेगळ्या खात्याद्वारे प्रदान केलेला भत्ता खर्च करण्याची परवानगी देते.
12 function safeTransferFrom(3 address token,4 address from,5 address to,6 uint256 value7 ) internal {8 // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));9 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));10 require(11 success && (data.length == 0 || abi.decode(data, (bool))),12 'TransferHelper::transferFrom: transferFrom failed'13 );14 }सर्व दाखवाहे फंक्शन ERC-20 ची transferFrom कार्यक्षमता (opens in a new tab) लागू करते, जे एका खात्याला वेगळ्या खात्याद्वारे प्रदान केलेला भत्ता खर्च करण्याची परवानगी देते.
12 function safeTransferETH(address to, uint256 value) internal {3 (bool success, ) = to.call{value: value}(new bytes(0));4 require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');5 }6}हे फंक्शन खात्यात इथर ट्रान्सफर करते. वेगळ्या कॉन्ट्रॅक्टला केलेला कोणताही कॉल इथर पाठवण्याचा प्रयत्न करू शकतो. कारण आपल्याला प्रत्यक्षात कोणत्याही फंक्शनला कॉल करण्याची आवश्यकता नाही, आपण कॉलसह कोणताही डेटा पाठवत नाही.
निष्कर्ष
हा सुमारे 50 पानांचा एक मोठा लेख आहे. जर तुम्ही इथपर्यंत पोहोचला असाल, तर तुमचे अभिनंदन! आशा आहे की आतापर्यंत तुम्हाला (लहान नमुना प्रोग्राम्सच्या तुलनेत) वास्तविक जीवनातील ॲप्लिकेशन लिहिताना कोणत्या गोष्टी विचारात घ्याव्या लागतात हे समजले असेल आणि तुम्ही तुमच्या स्वतःच्या युज केसेससाठी कॉन्ट्रॅक्ट्स अधिक चांगल्या प्रकारे लिहू शकाल.
आता जा आणि काहीतरी उपयुक्त लिहा आणि आम्हाला थक्क करा.
माझ्या अधिक कामासाठी येथे पहा (opens in a new tab).
पृष्ठ अखेरचे अद्यतन: ३ मार्च, २०२६
