यूनिस्वैप-v2 कॉन्ट्रैक्ट वॉक-थ्रू
परिचय
यूनिस्वैप v2 (opens in a new tab) किसी भी दो ERC-20 टोकन के बीच एक एक्सचेंज बाजार बना सकता है। इस लेख में हम इस प्रोटोकॉल को लागू करने वाले कॉन्ट्रैक्ट्स के सोर्स कोड पर जाएँगे और देखेंगे कि उन्हें इस तरह से क्यों लिखा गया है।
यूनिस्वैप क्या करता है?
मूल रूप से, दो प्रकार के यूज़र हैं: लिक्विडिटी प्रदाता और ट्रेडर।
लिक्विडिटी प्रदाता पूल को दो टोकन प्रदान करते हैं जिनका एक्सचेंज किया जा सकता है (हम उन्हें टोकन0 और टोकन1 कहेंगे)। बदले में, उन्हें एक तीसरा टोकन मिलता है जो पूल के आंशिक स्वामित्व का प्रतिनिधित्व करता है जिसे लिक्विडिटी टोकन कहा जाता है।
ट्रेडर्स एक प्रकार का टोकन पूल में भेजते हैं और लिक्विडिटी प्रदाताओं द्वारा प्रदान किए गए पूल में से दूसरा (उदाहरण के लिए, टोकन0 भेजें और टोकन1 प्राप्त करें) प्राप्त करते हैं। एक्सचेंज दर पूल में मौजूद टोकन0 और टोकन1 की सापेक्ष संख्या से निर्धारित होती है। इसके अलावा, पूल लिक्विडिटी पूल के लिए रिवॉर्ड के रूप में एक छोटा प्रतिशत लेता है।
जब लिक्विडिटी प्रदाता अपनी संपत्ति वापस चाहते हैं तो वे पूल टोकन बर्न कर सकते हैं और रिवॉर्ड में अपने हिस्से सहित अपने टोकन वापस प्राप्त कर सकते हैं।
अधिक विस्तृत विवरण के लिए यहां क्लिक करें (opens in a new tab)।
v2 क्यों? v3 क्यों नहीं?
यूनिस्वैप v3 (opens in a new tab) एक अपग्रेड है जो v2 की तुलना में बहुत अधिक जटिल है। पहले v2 सीखना और फिर v3 पर जाना आसान है।
कोर कॉन्ट्रैक्ट बनाम पेरिफेरी कॉन्ट्रैक्ट
यूनिस्वैप v2 को दो घटकों, एक कोर और एक पेरिफेरी में विभाजित किया गया है। यह विभाजन कोर कॉन्ट्रैक्ट्स, जो संपत्ति रखते हैं और इसलिए उन्हें सुरक्षित होना पड़ता है, को सरल और ऑडिट करने में आसान बनाने की अनुमति देता है। ट्रेडर्स द्वारा आवश्यक सभी अतिरिक्त फ़ंक्शनैलिटी तब पेरिफेरी कॉन्ट्रैक्ट्स द्वारा प्रदान की जा सकती है।
डेटा और कंट्रोल फ्लो
यह डेटा और कंट्रोल का फ्लो है जो तब होता है जब आप यूनिस्वैप की तीन मुख्य क्रियाएँ करते हैं:
- अलग-अलग टोकन के बीच स्वैप करें
- बाजार में लिक्विडिटी जोड़ें और पेयर एक्सचेंज ERC-20 लिक्विडिटी टोकन के साथ रिवॉर्ड प्राप्त करें
- ERC-20 लिक्विडिटी टोकन बर्न करें और वे ERC-20 टोकन वापस पाएं जिन्हें पेयर एक्सचेंज ट्रेडर्स को एक्सचेंज करने की अनुमति देता है
स्वैप
यह सबसे आम फ्लो है, जिसका उपयोग ट्रेडर्स द्वारा किया जाता है:
कॉलर
- पेरिफेरी खाते को स्वैप की जाने वाली राशि में एक भत्ता प्रदान करें।
- पेरिफेरी कॉन्ट्रैक्ट के कई स्वैप फ़ंक्शंस में से एक को कॉल करें (कौन सा इस बात पर निर्भर करता है कि ETH शामिल है या नहीं, क्या ट्रेडर जमा करने के लिए टोकन की राशि या वापस पाने के लिए टोकन की राशि निर्दिष्ट करता है, आदि)।
प्रत्येक स्वैप फ़ंक्शन एक
pathस्वीकार करता है, जो एक्सचेंजों की एक ऐरे है जिससे होकर गुजरना है।
पेरिफेरी कॉन्ट्रैक्ट में (UniswapV2Router02.sol)
- पथ के साथ प्रत्येक एक्सचेंज पर ट्रेड की जाने वाली मात्राओं की पहचान करें।
- पथ पर पुनरावृति करता है। रास्ते में हर एक्सचेंज के लिए यह इनपुट टोकन भेजता है और फिर एक्सचेंज के
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) वास्तविक पूल को लागू करता है जो टोकन का एक्सचेंज करता है। यह कोर यूनिस्वैप फ़ंक्शनैलिटी है।
1pragma solidity =0.5.16;2
3import './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;सेफमैथ लाइब्रेरी (opens in a new tab) का उपयोग ओवरफ्लो और अंडरफ्लो से बचने के लिए किया जाता है। यह महत्वपूर्ण है क्योंकि अन्यथा हम ऐसी स्थिति में पहुँच सकते हैं जहाँ एक मान -1 होना चाहिए, लेकिन इसके बजाय 2^256-1 होता है।
1 using UQ112x112 for uint224;पूल कॉन्ट्रैक्ट में बहुत सारी गणनाओं के लिए अंशों की आवश्यकता होती है। हालांकि, ईवीएम द्वारा अंशों का समर्थन नहीं किया जाता है।
यूनिस्वैप ने जो समाधान पाया वह 224 बिट मानों का उपयोग करना है, जिसमें पूर्णांक भाग के लिए 112 बिट्स और अंश के लिए 112 बिट्स हैं। तो 1.0 को 2^112 के रूप में दर्शाया गया है, 1.5 को 2^112 + 2^111 के रूप में दर्शाया गया है, आदि।
इस लाइब्रेरी के बारे में अधिक विवरण दस्तावेज़ में बाद में उपलब्ध हैं।
वेरिएबल्स
1 uint public constant MINIMUM_LIQUIDITY = 10**3;शून्य से विभाजन के मामलों से बचने के लिए, लिक्विडिटी टोकन की एक न्यूनतम संख्या है जो हमेशा मौजूद रहती है (लेकिन खाता शून्य के स्वामित्व में है)। वह संख्या न्यूनतम_लिक्विडिटी है, एक हजार।
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; // uses single storage slot, accessible via getReserves2 uint112 private reserve1; // uses single storage slot, accessible via getReservesप्रत्येक टोकन प्रकार के लिए पूल के पास जो रिज़र्व हैं। हम मानते हैं कि दोनों समान मूल्य का प्रतिनिधित्व करते हैं, और इसलिए प्रत्येक टोकन0 का मूल्य रिज़र्व1/रिज़र्व0 टोकन1 का है।
1 uint32 private blockTimestampLast; // uses single storage slot, accessible via getReservesअंतिम ब्लॉक के लिए टाइमस्टैम्प जिसमें एक एक्सचेंज हुआ था, जिसका उपयोग समय के साथ एक्सचेंज दरों को ट्रैक करने के लिए किया जाता है।
एथेरियम कॉन्ट्रैक्ट्स के सबसे बड़े गैस खर्चों में से एक भंडारण है, जो कॉन्ट्रैक्ट के एक कॉल से दूसरे तक बना रहता है। प्रत्येक भंडारण सेल 256 बिट लंबा होता है। तो तीन चर, रिज़र्व0, रिज़र्व1, और ब्लॉकटाइमस्टैम्पलास्ट, इस तरह से आवंटित किए जाते हैं कि एक एकल भंडारण मान में वे तीनों शामिल हो सकते हैं (112+112+32=256)।
1 uint public price0CumulativeLast;2 uint public price1CumulativeLast;ये चर प्रत्येक टोकन के लिए संचयी लागत रखते हैं (प्रत्येक दूसरे के संदर्भ में)। इनका उपयोग समय की अवधि में औसत एक्सचेंज दर की गणना के लिए किया जा सकता है।
1 uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity eventजिस तरह से पेयर एक्सचेंज टोकन0 और टोकन1 के बीच एक्सचेंज दर तय करता है, वह है ट्रे़ड के दौरान दो रिज़र्व के गुणक को स्थिर रखना। kLast यह मान है। यह तब बदलता है जब कोई लिक्विडिटी प्रदाता टोकन जमा करता है या निकालता है, और 0.3% बाजार शुल्क के कारण यह थोड़ा बढ़ जाता है।
यहाँ एक सरल उदाहरण है। ध्यान दें कि सरलता के लिए तालिका में दशमलव बिंदु के बाद केवल तीन अंक हैं, और हम 0.3% ट्रेडिंग शुल्क को अनदेखा करते हैं ताकि संख्याएँ सटीक न हों।
| इवेंट | रिज़र्व0 | रिज़र्व1 | रिज़र्व0 * रिज़र्व1 | औसत एक्सचेंज दर (टोकन1 / टोकन0) |
|---|---|---|---|---|
| प्रारंभिक सेटअप | 1,000.000 | 1,000.000 | 1,000,000 | |
| ट्रेडर A 47.619 टोकन1 के लिए 50 टोकन0 स्वैप करता है | 1,050.000 | 952.381 | 1,000,000 | 0.952 |
| ट्रेडर B 8.984 टोकन1 के लिए 10 टोकन0 स्वैप करता है | 1,060.000 | 943.396 | 1,000,000 | 0.898 |
| ट्रेडर C 34.305 टोकन1 के लिए 40 टोकन0 स्वैप करता है | 1,100.000 | 909.090 | 1,000,000 | 0.858 |
| ट्रेडर D 109.01 टोकन0 के लिए 100 टोकन1 स्वैप करता है | 990.990 | 1,009.090 | 1,000,000 | 0.917 |
| ट्रेडर E 10.079 टोकन1 के लिए 10 टोकन0 स्वैप करता है | 1,000.990 | 999.010 | 1,000,000 | 1.008 |
जैसे-जैसे ट्रेडर अधिक टोकन0 प्रदान करते हैं, आपूर्ति और मांग के आधार पर टोकन1 का सापेक्षिक मूल्य बढ़ता है, और इसके विपरीत।
लॉक
1 uint private unlocked = 1;रीएंट्रेंसी एब्यूज (opens in a new tab) पर आधारित सुरक्षा कमजोरियों का एक वर्ग है। यूनिस्वैप को मनमाना ERC-20 टोकन ट्रांसफर करने की आवश्यकता है, जिसका अर्थ है कि ERC-20 कॉन्ट्रैक्ट्स को कॉल करना जो उन्हें कॉल करने वाले यूनिस्वैप बाजार का दुरुपयोग करने का प्रयास कर सकते हैं।
कॉन्ट्रैक्ट के हिस्से के रूप में एक 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 }यह फ़ंक्शन कॉल करने वालों को एक्सचेंज की वर्तमान स्थिति प्रदान करता है। ध्यान दें कि सॉलिडिटी फ़ंक्शन कई मान लौटा सकते हैं (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 }प्रत्येक लागत संचयक को नवीनतम लागत (दूसरे टोकन का रिजर्व/इस टोकन का रिजर्व) को सेकंड में बीते समय से गुणा करके अपडेट किया जाता है। औसत मूल्य प्राप्त करने के लिए, आप समय में दो बिंदुओं पर संचयी मूल्य पढ़ते हैं और उनके बीच के समय के अंतर से विभाजित करते हैं। उदाहरण के लिए, घटनाओं के इस क्रम को मान लें:
| इवेंट | रिज़र्व0 | रिज़र्व1 | टाइमस्टैम्प | सीमांत विनिमय दर (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) {यूनिस्वैप 2.0 में व्यापारी बाजार का उपयोग करने के लिए 0.30% शुल्क का भुगतान करते हैं। उस शुल्क का अधिकांश हिस्सा (व्यापार का 0.25%) हमेशा लिक्विडिटी प्रदाताओं के पास जाता है। शेष 0.05% या तो लिक्विडिटी प्रदाताओं को जा सकता है या फ़ैक्टरी द्वारा निर्दिष्ट पते पर प्रोटोकॉल शुल्क के रूप में जा सकता है, जो यूनिस्वैप को उनके विकास के प्रयास के लिए भुगतान करता है।
गणना (और इसलिए गैस लागत) को कम करने के लिए, यह शुल्क केवल तब गणना की जाती है जब लिक्विडिटी जोड़ी जाती है या पूल से हटा दी जाती है, न कि प्रत्येक लेनदेन पर।
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) थी जिसने अनुबंधों को उन भंडारण को शून्य करके एथेरियम स्थिति के समग्र आकार को कम करने के लिए प्रोत्साहित किया जिनकी उन्हें आवश्यकता नहीं थी।
यह कोड संभव होने पर वह वापसी प्राप्त करता है।
बाहरी रूप से सुलभ फ़ंक्शन
ध्यान दें कि जबकि कोई भी लेनदेन या अनुबंध इन कार्यों को कॉल कर सकता है, उन्हें पेरिफेरी अनुबंध से कॉल करने के लिए डिज़ाइन किया गया है। यदि आप उन्हें सीधे कॉल करते हैं तो आप पेयर एक्सचेंज को धोखा नहीं दे पाएंगे, लेकिन आप गलती से मूल्य खो सकते हैं।
मिंट
1 // इस निम्न-स्तरीय फ़ंक्शन को एक अनुबंध से कॉल किया जाना चाहिए जो महत्वपूर्ण सुरक्षा जांच करता है2 function mint(address to) external lock returns (uint liquidity) {यह फ़ंक्शन तब कॉल किया जाता है जब कोई लिक्विडिटी प्रदाता पूल में लिक्विडिटी जोड़ता है। यह इनाम के रूप में अतिरिक्त लिक्विडिटी टोकन मिंट करता है। इसे एक पेरिफेरी अनुबंध से कॉल किया जाना चाहिए जो उसी लेनदेन में लिक्विडिटी जोड़ने के बाद इसे कॉल करता है (ताकि कोई और वैध मालिक से पहले नई लिक्विडिटी का दावा करने वाला लेनदेन सबमिट न कर सके)।
1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // गैस की बचतयह एक सॉलिडिटी फ़ंक्शन के परिणामों को पढ़ने का तरीका है जो कई मान लौटाता है। हम अंतिम लौटे मान, ब्लॉक टाइमस्टैम्प को त्याग देते हैं, क्योंकि हमें इसकी आवश्यकता नहीं है।
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 इससे मूल्य निकालने के लिए अधिक मूल्यवान है।
| इवेंट | रिज़र्व0 | रिज़र्व1 | रिज़र्व0 * रिज़र्व1 | पूल का मूल्य (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 में परिवर्तन के वर्गमूल के बराबर है और लिक्विडिटी टोकन का मूल्य नहीं बदलता है (जब तक कि हमें ऐसी जमा राशि न मिले जिसमें दोनों प्रकार के समान मान न हों, इस मामले में "जुर्माना" वितरित हो जाता है)। यहां समान मूल्य वाले दो टोकन के साथ एक और उदाहरण है, जिसमें तीन अच्छी जमा और एक खराब (केवल एक टोकन प्रकार की जमा, इसलिए यह कोई लिक्विडिटी टोकन उत्पन्न नहीं करता है)।
| इवेंट | रिज़र्व0 | रिज़र्व1 | रिज़र्व0 * रिज़र्व1 | पूल मूल्य (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 फ़ंक्शन का उपयोग करें।
1
2 _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) को अपडेट करें और उचित इवेंट उत्सर्जित करें।
बर्न
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; // शेष का उपयोग आनुपातिक वितरण सुनिश्चित करता है4 amount1 = liquidity.mul(balance1) / _totalSupply; // शेष का उपयोग आनुपातिक वितरण सुनिश्चित करता है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));6
7 _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 फ़ंक्शन का दर्पण प्रतिबिंब है।
स्वैप करें
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');4
5 uint balance0;6 uint balance1;7 { // _token{0,1} के लिए स्कोप, बहुत गहरे स्टैक त्रुटियों से बचाता हैस्थानीय चर या तो मेमोरी में संग्रहीत किए जा सकते हैं या, यदि उनमें से बहुत अधिक नहीं हैं, तो सीधे स्टैक पर। यदि हम संख्या को सीमित कर सकते हैं तो हम स्टैक का उपयोग करेंगे जिससे हम कम गैस का उपयोग करेंगे। अधिक विवरण के लिए येलो पेपर, औपचारिक एथेरियम विनिर्देश (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); // आशावादी रूप से टोकन स्थानांतरित करें5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // आशावादी रूप से टोकन स्थानांतरित करेंयह हस्तांतरण आशावादी है, क्योंकि हम यह सुनिश्चित करने से पहले हस्तांतरण करते हैं कि सभी शर्तें पूरी हों। यह एथेरियम में ठीक है क्योंकि यदि कॉल में बाद में शर्तें पूरी नहीं होती हैं तो हम इससे और इसके द्वारा बनाए गए किसी भी बदलाव से बाहर हो जाते हैं।
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 के लिए स्कोप, बहुत गहरे स्टैक त्रुटियों से बचाता है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 }2
3 _update(balance0, balance1, _reserve0, _reserve1);4 emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);5 }reserve0 और reserve1 को अपडेट करें, और यदि आवश्यक हो तो मूल्य संचयकों और टाइमस्टैम्प को अपडेट करें और एक इवेंट उत्सर्जित करें।
सिंक या स्किम करें
वास्तविक शेष राशि उन रिज़र्व से अलग हो सकती है जो पेयर एक्सचेंज के पास हैं।
अनुबंध की सहमति के बिना टोकन निकालने का कोई तरीका नहीं है, लेकिन जमा एक अलग मामला है। एक खाता 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 }8
9
10
11 // रिजर्व को शेष राशि से मिलान करने के लिए मजबूर करें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;2
3import './interfaces/IUniswapV2Factory.sol';4import './UniswapV2Pair.sol';5
6contract 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, एक ऐरे है जिसमें इस फ़ैक्टरी द्वारा बनाए गए पेयर एक्सचेंजों के सभी पते शामिल हैं। एथेरियम में आप मैपिंग की सामग्री पर पुनरावृति नहीं कर सकते हैं, या सभी कुंजियों की सूची प्राप्त नहीं कर सकते हैं, इसलिए यह चर यह जानने का एकमात्र तरीका है कि यह फ़ैक्टरी किन एक्सचेंजों का प्रबंधन करती है।
नोट: आप किसी मैपिंग की सभी कुंजियों पर पुनरावृति नहीं कर सकते हैं इसका कारण यह है कि अनुबंध डेटा भंडारण महंगा है, इसलिए हम इसका जितना कम उपयोग करेंगे उतना बेहतर होगा, और हम इसे जितनी कम बार बदलेंगे उतना बेहतर होगा। आप पुनरावृति का समर्थन करने वाली मैपिंग (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 टोकन के बीच एक जोड़ी एक्सचेंज बनाना। ध्यान दें कि कोई भी इस फ़ंक्शन को कॉल कर सकता है। नया पेयर एक्सचेंज बनाने के लिए आपको यूनिस्वैप से अनुमति की आवश्यकता नहीं है।
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 बाइटकोड को मेमोरी में लिखता है)। आम तौर पर सॉलिडिटी में हम केवल addr = new <name of contract>(<constructor parameters>) का उपयोग करते हैं और कंपाइलर हमारे लिए सब कुछ का ख्याल रखता है, लेकिन एक नियतात्मक अनुबंध पता रखने के लिए हमें CREATE2 ऑपकोड (opens in a new tab) का उपयोग करने की आवश्यकता है।
जब यह कोड लिखा गया था तब वह ऑपकोड अभी तक सॉलिडिटी द्वारा समर्थित नहीं था, इसलिए मैन्युअल रूप से कोड प्राप्त करना आवश्यक था। यह अब कोई समस्या नहीं है, क्योंकि सॉलिडिटी अब 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 }जब कोई ऑपकोड अभी तक सॉलिडिटी द्वारा समर्थित नहीं है तो हम उसे इनलाइन असेंबली (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 }5
6 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 लिक्विडिटी टोकन को लागू करता है। यह ओपनज़ेपेलिन ERC-20 अनुबंध के समान है, इसलिए मैं केवल उस हिस्से की व्याख्या करूँगा जो अलग है, permit कार्यक्षमता।
एथेरियम पर लेनदेन में ईथर (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) को पुनः प्राप्त करने के लिए कोड है। यह यूल (opens in a new tab) नामक एक EVM असेंबली बोली का उपयोग करता है। ध्यान दें कि यूल के वर्तमान संस्करण में आपको 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(...) वह संदेश है जिसे हम प्राप्त करने की उम्मीद करते हैं। हम जानते हैं कि नॉन्स क्या होना चाहिए, इसलिए हमें इसे पैरामीटर के रूप में प्राप्त करने की कोई आवश्यकता नहीं है।
एथेरियम हस्ताक्षर एल्गोरिदम को हस्ताक्षर करने के लिए 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) के रूप में मानें।
पेरिफेरी कॉन्ट्रैक्ट्स
पेरिफेरी कॉन्ट्रैक्ट्स यूनिस्वैप के लिए 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)।
1pragma solidity =0.6.6;2
3import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';4import '@uniswap/lib/contracts/libraries/TransferHelper.sol';5
6import './interfaces/IUniswapV2Router02.sol';7import './libraries/UniswapV2Library.sol';8import './libraries/SafeMath.sol';9import './interfaces/IERC20.sol';10import './interfaces/IWETH.sol';इनमें से अधिकांश का हम या तो पहले सामना कर चुके हैं, या काफी स्पष्ट हैं। एकमात्र अपवाद IWETH.sol है। यूनिस्वैप v2 किसी भी ERC-20 टोकन के जोड़े के लिए एक्सचेंज की अनुमति देता है, लेकिन ईथर (ETH) स्वयं एक ERC-20 टोकन नहीं है। यह मानक से पहले का है और अद्वितीय तंत्रों द्वारा स्थानांतरित किया जाता है। ERC-20 टोकन पर लागू होने वाले अनुबंधों में ETH के उपयोग को सक्षम करने के लिए लोग रैप्ड ईथर (WETH) (opens in a new tab) अनुबंध के साथ आए। आप इस अनुबंध को ETH भेजते हैं, और यह आपके लिए WETH की बराबर राशि मिंट करता है। या आप WETH को बर्न कर सकते हैं, और ETH वापस पा सकते हैं।
1contract UniswapV2Router02 is IUniswapV2Router02 {2 using SafeMath for uint;3
4 address public immutable override factory;5 address public immutable override WETH;राउटर को यह जानने की जरूरत है कि किस फैक्ट्री का उपयोग करना है, और उन लेनदेन के लिए जिन्हें WETH की आवश्यकता है, किस WETH अनुबंध का उपयोग करना है। ये मान अपरिवर्तनीय (opens in a new tab) हैं, जिसका अर्थ है कि उन्हें केवल कंस्ट्रक्टर में ही सेट किया जा सकता है। यह यूज़र्स को विश्वास दिलाता है कि कोई भी उन्हें कम ईमानदार अनुबंधों की ओर इंगित करने के लिए नहीं बदल पाएगा।
1 modifier ensure(uint deadline) {2 require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');3 _;4 }यह संशोधक यह सुनिश्चित करता है कि समय-सीमित लेनदेन ("यदि आप कर सकते हैं तो समय 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 में भुनाते हैं। केवल हम जिस WETH अनुबंध का उपयोग करते हैं वह ऐसा करने के लिए अधिकृत है।
लिक्विडिटी जोड़ें
ये फ़ंक्शन पेयर एक्सचेंज में टोकन जोड़ते हैं, जो लिक्विडिटी पूल को बढ़ाता है।
1
2 // **** लिक्विडिटी जोड़ें ****3 function _addLiquidity(यह फ़ंक्शन A और B टोकन की राशि की गणना करने के लिए उपयोग किया जाता है जिसे जोड़ी एक्सचेंज में जमा किया जाना चाहिए।
1 address tokenA,2 address tokenB,ये ERC-20 टोकन अनुबंधों के पते हैं।
1 uint amountADesired,2 uint amountBDesired,ये वे राशियाँ हैं जिन्हें लिक्विडिटी प्रदाता जमा करना चाहता है। वे जमा किए जाने वाले A और B की अधिकतम राशि भी हैं।
1 uint amountAMin,2 uint amountBMinये जमा करने के लिए न्यूनतम स्वीकार्य राशियाँ हैं। यदि लेनदेन इन राशियों या अधिक के साथ नहीं हो सकता है, तो इससे बाहर निकलें। यदि आप यह सुविधा नहीं चाहते हैं, तो बस शून्य निर्दिष्ट करें।
लिक्विडिटी प्रदाता आमतौर पर एक न्यूनतम निर्दिष्ट करते हैं, क्योंकि वे लेनदेन को एक विनिमय दर तक सीमित करना चाहते हैं जो वर्तमान के करीब हो। यदि विनिमय दर बहुत अधिक उतार-चढ़ाव करती है तो इसका मतलब यह हो सकता है कि समाचार जो अंतर्निहित मूल्यों को बदलते हैं, और वे मैन्युअल रूप से तय करना चाहते हैं कि क्या करना है।
उदाहरण के लिए, एक ऐसे मामले की कल्पना करें जहां विनिमय दर एक से एक है और लिक्विडिटी प्रदाता इन मूल्यों को निर्दिष्ट करता है:
| पैरामीटर | मूल्य |
|---|---|
| amountADesired | 1000 |
| amountBDesired | 1000 |
| amountAMin | 900 |
| amountBMin | 800 |
जब तक विनिमय दर 0.9 और 1.25 के बीच रहती है, तब तक लेनदेन होता है। यदि विनिमय दर उस सीमा से बाहर हो जाती है, तो लेनदेन रद्द हो जाता है।
इस सावधानी का कारण यह है कि लेनदेन तत्काल नहीं होते हैं, आप उन्हें सबमिट करते हैं और अंततः एक सत्यापनकर्ता उन्हें एक ब्लॉक में शामिल करेगा (जब तक कि आपकी गैस की कीमत बहुत कम न हो, उस स्थिति में आपको उसी नॉन्स और उच्च गैस मूल्य के साथ एक और लेनदेन सबमिट करना होगा ताकि इसे ओवरराइट किया जा सके)। आप सबमिशन और समावेशन के बीच के अंतराल में क्या होता है, इसे नियंत्रित नहीं कर सकते।
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) का उपयोग करके इष्टतम राशि प्राप्त करते हैं। हम वर्तमान रिज़र्व के समान अनुपात चाहते हैं।
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);बदले में to पते को पूल के आंशिक स्वामित्व के लिए लिक्विडिटी टोकन दें। मुख्य अनुबंध का mint फ़ंक्शन देखता है कि उसके पास कितने अतिरिक्त टोकन हैं (पिछली बार लिक्विडिटी बदलने के समय की तुलना में) और तदनुसार लिक्विडिटी मिंट करता है।
1 function addLiquidityETH(2 address token,3 uint amountTokenDesired,जब कोई लिक्विडिटी प्रदाता किसी टोकन/ETH जोड़ी एक्सचेंज को लिक्विडिटी प्रदान करना चाहता है, तो कुछ अंतर होते हैं। अनुबंध लिक्विडिटी प्रदाता के लिए ETH को रैप करने का काम करता है। यह निर्दिष्ट करने की कोई आवश्यकता नहीं है कि यूज़र कितने 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 // यदि कोई है, तो डस्ट eth वापस करें3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);4 }यूज़र ने हमें पहले ही ETH भेज दिया है, इसलिए यदि कोई अतिरिक्त बचा है (क्योंकि दूसरा टोकन यूज़र के विचार से कम मूल्यवान है), तो हमें रिफंड जारी करने की आवश्यकता है।
लिक्विडिटी हटाएं
ये फ़ंक्शन लिक्विडिटी हटाएंगे और लिक्विडिटी प्रदाता को वापस भुगतान करेंगे।
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) {लिक्विडिटी हटाने का सबसे सरल मामला। प्रत्येक टोकन की एक न्यूनतम राशि है जिसे लिक्विडिटी प्रदाता स्वीकार करने के लिए सहमत है, और यह समय सीमा से पहले होना चाहिए।
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 }16
17
18 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 }ये फ़ंक्शन मेटा-लेनदेन को रिले करते हैं ताकि बिना ईथर वाले यूज़र्स पूल से निकाल सकें, अनुमति तंत्र का उपयोग करके।
1
2 // **** लिक्विडिटी हटाएं (शुल्क-पर-हस्तांतरण टोकन का समर्थन) ****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 फ़ंक्शन पर भरोसा नहीं कर सकते हैं कि हमें कितना टोकन वापस मिलेगा, इसलिए हमें पहले निकालना होगा और फिर शेष राशि प्राप्त करनी होगी।
1
2
3 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 // **** स्वैप ****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)। इसके बजाय, स्वैप फंक्शन एक पथ की अवधारणा का समर्थन करते हैं। एक व्यापारी A के लिए B, B के लिए C, और C के लिए D का एक्सचेंज कर सकता है, इसलिए सीधे A-D जोड़ी एक्सचेंज की कोई आवश्यकता नहीं है।
इन बाजारों पर कीमतें सिंक्रनाइज़ होती हैं, क्योंकि जब वे सिंक से बाहर हो जाती हैं तो यह आर्बिट्रेज का अवसर पैदा करता है। उदाहरण के लिए, तीन टोकन, A, B, और C की कल्पना करें। तीन जोड़ी एक्सचेंज हैं, प्रत्येक जोड़ी के लिए एक।
- प्रारंभिक स्थिति
- एक व्यापारी 24.695 A टोकन बेचता है और 25.305 B टोकन प्राप्त करता है।
- व्यापारी 24.695 B टोकन 25.305 C टोकन के लिए बेचता है, लाभ के रूप में लगभग 0.61 B टोकन रखता है।
- फिर व्यापारी 24.695 C टोकन 25.305 A टोकन के लिए बेचता है, लाभ के रूप में लगभग 0.61 C टोकन रखता है। व्यापारी के पास 0.61 अतिरिक्त A टोकन भी हैं (25.305 जो व्यापारी को अंत में मिलते हैं, माइनस 24.695 का मूल निवेश)।
| चरण | A-B एक्सचेंज | B-C एक्सचेंज | A-C एक्सचेंज |
|---|---|---|---|
| 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];उस जोड़ी को प्राप्त करें जिसे हम वर्तमान में संभाल रहे हैं, इसे (जोड़ी के साथ उपयोग के लिए) क्रमबद्ध करें और अपेक्षित आउटपुट राशि प्राप्त करें।
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;क्या यह अंतिम एक्सचेंज है? यदि हां, तो ट्रेड के लिए प्राप्त टोकन को गंतव्य पर भेजें। यदि नहीं, तो इसे अगले जोड़ी एक्सचेंज पर भेजें।
1
2 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(3 amount0Out, amount1Out, to, new bytes(0)4 );5 }6 }वास्तव में टोकन को स्वैप करने के लिए जोड़ी एक्सचेंज को कॉल करें। हमें एक्सचेंज के बारे में बताने के लिए कॉलबैक की आवश्यकता नहीं है, इसलिए हम उस फ़ील्ड में कोई बाइट नहीं भेजते हैं।
1 function swapExactTokensForTokens(यह फंक्शन सीधे व्यापारियों द्वारा एक टोकन को दूसरे के लिए स्वैप करने के लिए उपयोग किया जाता है।
1 uint amountIn,2 uint amountOutMin,3 address[] calldata path,इस पैरामीटर में ERC-20 अनुबंधों के पते होते हैं। जैसा कि ऊपर बताया गया है, यह एक ऐरे है क्योंकि आपके पास जो संपत्ति है, उससे अपनी इच्छित संपत्ति प्राप्त करने के लिए आपको कई जोड़ी एक्सचेंजों से गुजरना पड़ सकता है।
सॉलिडिटी में एक फंक्शन पैरामीटर को memory या calldata में संग्रहीत किया जा सकता है। यदि फंक्शन अनुबंध का एक एंट्री पॉइंट है, जिसे सीधे उपयोगकर्ता (लेन-देन का उपयोग करके) या किसी भिन्न अनुबंध से कॉल किया जाता है, तो पैरामीटर का मान सीधे कॉल डेटा से लिया जा सकता है। यदि फंक्शन को आंतरिक रूप से कॉल किया जाता है, जैसा कि ऊपर _swap है, तो पैरामीटर को memory में संग्रहीत किया जाना चाहिए। कॉल किए गए अनुबंध के परिप्रेक्ष्य से calldata केवल-पढ़ने के लिए है।
uint या address जैसे स्केलर प्रकारों के साथ, कंपाइलर हमारे लिए भंडारण का विकल्प संभालता है, लेकिन ऐरे के साथ, जो लंबे और अधिक महंगे होते हैं, हम उपयोग किए जाने वाले भंडारण के प्रकार को निर्दिष्ट करते हैं।
1 address to,2 uint deadline3 ) external virtual override ensure(deadline) returns (uint[] memory amounts) {रिटर्न मान हमेशा मेमोरी में लौटाए जाते हैं।
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, एक व्यापारी को इनपुट टोकन की सटीक संख्या निर्दिष्ट करने की अनुमति देता है जिसे वह देने को तैयार है और आउटपुट टोकन की न्यूनतम संख्या जिसे वह बदले में प्राप्त करने को तैयार है। यह फंक्शन रिवर्स स्वैप करता है, यह एक व्यापारी को आउटपुट टोकन की संख्या निर्दिष्ट करने देता है जो वह चाहता है, और इनपुट टोकन की अधिकतम संख्या जिसे वह उनके लिए भुगतान करने को तैयार है।
दोनों ही मामलों में, व्यापारी को पहले इस पेरिफेरी अनुबंध को उन्हें स्थानांतरित करने की अनुमति देने के लिए एक भत्ता देना होगा।
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 }16
17
18 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 }35
36
37
38 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 }55
56
57 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 // अगर कोई डस्ट ईटीएच है, तो उसे वापस करें72 if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);73 }इन चार वेरिएंट में 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 { // स्टैक बहुत गहरी त्रुटियों से बचने के लिए स्कोप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 }7
8
9 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 }26
27
28 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 }51
52
53 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 }5
6 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 }15
16 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 }25
26 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 }35
36 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}ये फंक्शन केवल प्रॉक्सी हैं जो UniswapV2Library फंक्शन को कॉल करते हैं।
UniswapV2Migrator.sol
इस अनुबंध का उपयोग पुराने v1 से v2 में एक्सचेंज को माइग्रेट करने के लिए किया गया था। अब जब वे माइग्रेट हो गए हैं, तो यह अब प्रासंगिक नहीं है।
लाइब्रेरियां
SafeMath लाइब्रेरी (opens in a new tab) अच्छी तरह से प्रलेखित है, इसलिए इसे यहां प्रलेखित करने की कोई आवश्यकता नहीं है।
गणित
इस लाइब्रेरी में कुछ गणित फंक्शन हैं जिनकी सामान्य रूप से सॉलिडिटी कोड में आवश्यकता नहीं होती है, इसलिए वे भाषा का हिस्सा नहीं हैं।
1pragma solidity =0.5.16;2
3// विभिन्न गणित संचालन करने के लिए एक लाइब्रेरी4
5library Math {6 function min(uint x, uint y) internal pure returns (uint z) {7 z = x < y ? x : y;8 }9
10 // बेबीलोनियन विधि (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)
यह लाइब्रेरी भिन्नों को संभालती है, जो सामान्य रूप से एथेरियम अंकगणित का हिस्सा नहीं हैं। यह संख्या x को x*2^112 के रूप में एन्कोड करके ऐसा करता है। यह हमें बिना किसी बदलाव के मूल जोड़ और घटाव ऑपकोड का उपयोग करने देता है।
1pragma solidity =0.5.16;2
3// बाइनरी फिक्स्ड पॉइंट नंबरों को संभालने के लिए एक लाइब्रेरी (https://wikipedia.org/wiki/Q_(number_format))4
5// रेंज: [0, 2**112 - 1]6// रिज़ॉल्यूशन: 1 / 2**1127
8library 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;2
3import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';4
5import "./SafeMath.sol";6
7library UniswapV2Library {8 using SafeMath for uint;9
10 // क्रमबद्ध टोकन पते लौटाता है, इस क्रम में क्रमबद्ध जोड़े से वापसी मानों को संभालने के लिए उपयोग किया जाता है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' // इनिट कोड हैश9 ))));10 }यह फंक्शन दो टोकन के लिए जोड़ी एक्सचेंज के पते की गणना करता है। यह अनुबंध CREATE2 ऑपकोड (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 }यह फंक्शन आपको टोकन B की वह राशि बताता है जो आपको टोकन A के बदले में मिलेगी यदि कोई शुल्क शामिल नहीं है। यह गणना इस बात को ध्यान में रखती है कि हस्तांतरण से विनिमय दर बदल जाती है।
1 // किसी संपत्ति की इनपुट राशि और जोड़ी भंडार को देखते हुए, दूसरी संपत्ति की अधिकतम आउटपुट राशि लौटाता है2 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {ऊपर दिया गया quote फंक्शन बहुत अच्छा काम करता है यदि जोड़ी एक्सचेंज का उपयोग करने के लिए कोई शुल्क नहीं है। हालांकि, यदि 0.3% एक्सचेंज शुल्क है तो आपको वास्तव में मिलने वाली राशि कम है। यह फंक्शन एक्सचेंज शुल्क के बाद की राशि की गणना करता है।
1
2 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 }सॉलिडिटी मूल रूप से भिन्नों को नहीं संभालती है, इसलिए हम केवल राशि को 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 }यह फंक्शन लगभग वही काम करता है, लेकिन यह आउटपुट राशि प्राप्त करता है और इनपुट प्रदान करता है।
1
2 // किसी भी संख्या में जोड़े पर श्रृंखलाबद्ध 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 }12
13 // किसी भी संख्या में जोड़े पर श्रृंखलाबद्ध 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 और एथेरियम हस्तांतरण के आसपास सफलता जांच जोड़ती है ताकि एक रिवर्ट और एक false मान रिटर्न का एक ही तरह से इलाज किया जा सके।
1// SPDX-License-Identifier: GPL-3.0-or-later2
3pragma solidity >=0.6.0;4
5// ERC20 टोकन के साथ इंटरैक्ट करने और ETH भेजने के लिए सहायक तरीके जो लगातार सही/गलत नहीं लौटाते हैं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 मिलता है) विफल हो सकता है।
1
2
3 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) को लागू करता है, जो एक खाते को एक अलग खाते द्वारा प्रदान किए गए भत्ते को खर्च करने की अनुमति देता है।
1
2 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) को लागू करता है, जो एक खाते को एक अलग खाते द्वारा प्रदान किए गए भत्ते को खर्च करने की अनुमति देता है।
1
2 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)।
पेज का अंतिम अपडेट: 3 मार्च 2026
