யூனிஸ்வாப்-v2 ஒப்பந்தத்தின் வழிகாட்டி
அறிமுகம்
யூனிஸ்வாப் v2 (opens in a new tab) எந்த இரண்டு ERC-20 டோக்கன்களுக்கும் இடையே ஒரு பரிமாற்ற சந்தையை உருவாக்க முடியும். இந்தக் கட்டுரையில், இந்த நெறிமுறையைச் செயல்படுத்தும் ஒப்பந்தங்களுக்கான மூலக் குறியீட்டைப் பற்றிப் பார்ப்போம், மேலும் அவை ஏன் அவ்வாறு எழுதப்பட்டுள்ளன என்பதையும் காண்போம்.
யூனிஸ்வாப் என்ன செய்கிறது?
அடிப்படையில், இரண்டு வகையான பயனர்கள் உள்ளனர்: நீர்மை வழங்குபவர்கள் மற்றும் வர்த்தகர்கள்.
நீர்மை வழங்குபவர்கள் பரிமாறிக்கொள்ளக்கூடிய இரண்டு டோக்கன்களை தொகுப்பிற்கு வழங்குகிறார்கள் (அவற்றை நாம் Token0 மற்றும் Token1 என்று அழைப்போம்). அதற்குப் பதிலாக, தொகுப்பின் பகுதி உரிமையைக் குறிக்கும் நீர்மை டோக்கன் எனப்படும் மூன்றாவது டோக்கனைப் பெறுகிறார்கள்.
வர்த்தகர்கள் ஒரு வகையான டோக்கனைத் தொகுப்பிற்கு அனுப்பி, நீர்மை வழங்குபவர்களால் வழங்கப்பட்ட தொகுப்பிலிருந்து மற்றொன்றைப் பெறுகிறார்கள் (எடுத்துக்காட்டாக, Token0-ஐ அனுப்பி Token1-ஐப் பெறுதல்). பரிமாற்ற விகிதமானது தொகுப்பில் உள்ள Token0 மற்றும் Token1 ஆகியவற்றின் ஒப்பீட்டு எண்ணிக்கையால் தீர்மானிக்கப்படுகிறது. கூடுதலாக, நீர்மைத் தொகுப்பிற்கான வெகுமதியாக ஒரு சிறிய சதவீதத்தை தொகுப்பு எடுத்துக்கொள்கிறது.
நீர்மை வழங்குபவர்கள் தங்கள் சொத்துக்களைத் திரும்பப் பெற விரும்பினால், அவர்கள் தொகுப்பு டோக்கன்களை எரித்து, வெகுமதிகளில் தங்கள் பங்கு உட்பட, தங்கள் டோக்கன்களைத் திரும்பப் பெறலாம்.
முழுமையான விளக்கத்திற்கு இங்கே கிளிக் செய்யவும் (opens in a new tab).
ஏன் v2? ஏன் v3 இல்லை?
யூனிஸ்வாப் v3 (opens in a new tab) என்பது v2-ஐ விட மிகவும் சிக்கலான ஒரு மேம்படுத்தலாகும். முதலில் v2-ஐக் கற்றுக்கொண்டு பின்னர் v3-க்குச் செல்வது எளிது.
மைய ஒப்பந்தங்கள் மற்றும் புற ஒப்பந்தங்கள்
யூனிஸ்வாப் v2 இரண்டு கூறுகளாகப் பிரிக்கப்பட்டுள்ளது, ஒரு மையம் (core) மற்றும் ஒரு புறம் (periphery). இந்தப் பிரிப்பானது, சொத்துக்களை வைத்திருக்கும் மற்றும் பாதுகாப்பாக இருக்க வேண்டிய மைய ஒப்பந்தங்களை எளிமையாகவும் தணிக்கை செய்ய எளிதாகவும் இருக்க அனுமதிக்கிறது. வர்த்தகர்களுக்குத் தேவையான அனைத்து கூடுதல் செயல்பாடுகளையும் புற ஒப்பந்தங்கள் மூலம் வழங்க முடியும்.
தரவு மற்றும் கட்டுப்பாட்டு ஓட்டங்கள்
யூனிஸ்வாப்பின் மூன்று முக்கிய செயல்களை நீங்கள் செய்யும்போது நிகழும் தரவு மற்றும் கட்டுப்பாட்டு ஓட்டம் இதுதான்:
- வெவ்வேறு வில்லைகளுக்கு இடையே பரிமாற்றம் செய்தல்
- சந்தைக்கு நீர்மைத்தன்மையைச் சேர்த்து, ஜோடி பரிமாற்ற ERC-20 நீர்மை டோக்கன்களை வெகுமதியாகப் பெறுதல்
- ERC-20 நீர்மை டோக்கன்களை எரித்து, வர்த்தகர்கள் பரிமாற்றம் செய்ய ஜோடி பரிமாற்றம் அனுமதிக்கும் ERC-20 வில்லைகளைத் திரும்பப் பெறுதல்
பரிமாற்றம்
இது வர்த்தகர்களால் பயன்படுத்தப்படும் மிகவும் பொதுவான ஓட்டமாகும்:
அழைப்பாளர்
- பரிமாற்றம் செய்யப்பட வேண்டிய தொகைக்கான அனுமதித்தொகையை periphery கணக்கிற்கு வழங்கவும்.
- periphery ஒப்பந்தத்தின் பல பரிமாற்றச் செயற்கூறுகளில் ஒன்றை அழைக்கவும் (எதை அழைப்பது என்பது ETH உள்ளதா இல்லையா, வர்த்தகர் டெபாசிட் செய்ய வேண்டிய வில்லைகளின் அளவைக் குறிப்பிடுகிறாரா அல்லது திரும்பப் பெற வேண்டிய வில்லைகளின் அளவைக் குறிப்பிடுகிறாரா என்பதைப் பொறுத்தது).
ஒவ்வொரு பரிமாற்றச் செயற்கூறும் கடந்து செல்ல வேண்டிய பரிமாற்றங்களின் வரிசையான
pathஐ ஏற்றுக்கொள்கிறது.
periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)
- பாதையில் உள்ள ஒவ்வொரு பரிமாற்றத்திலும் வர்த்தகம் செய்யப்பட வேண்டிய தொகைகளை அடையாளம் காணவும்.
- பாதையின் மீது மீண்டும் செயல்படுகிறது. வழியில் உள்ள ஒவ்வொரு பரிமாற்றத்திற்கும் இது உள்ளீட்டு வில்லையை அனுப்புகிறது, பின்னர் பரிமாற்றத்தின்
swapசெயற்கூறை அழைக்கிறது. பெரும்பாலான சந்தர்ப்பங்களில் வில்லைகளுக்கான இலக்கு முகவரி என்பது பாதையில் உள்ள அடுத்த ஜோடி பரிமாற்றமாகும். இறுதிப் பரிமாற்றத்தில் இது வர்த்தகரால் வழங்கப்பட்ட முகவரியாகும்.
மைய ஒப்பந்தத்தில் (UniswapV2Pair.sol)
- மைய ஒப்பந்தம் ஏமாற்றப்படவில்லை என்பதையும், பரிமாற்றத்திற்குப் பிறகு போதுமான நீர்மைத்தன்மையைப் பராமரிக்க முடியும் என்பதையும் சரிபார்க்கவும்.
- அறியப்பட்ட இருப்புகளுக்கு மேலதிகமாக நம்மிடம் எத்தனை கூடுதல் வில்லைகள் உள்ளன என்பதைப் பார்க்கவும். அந்தத் தொகைதான் பரிமாற்றம் செய்ய நாம் பெற்ற உள்ளீட்டு வில்லைகளின் எண்ணிக்கையாகும்.
- வெளியீட்டு வில்லைகளை இலக்கிற்கு அனுப்பவும்.
- இருப்புத் தொகைகளைப் புதுப்பிக்க
_updateஐ அழைக்கவும்
மீண்டும் periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)
- தேவையான ஏதேனும் சுத்திகரிப்புகளைச் செய்யவும் (எடுத்துக்காட்டாக, வர்த்தகருக்கு அனுப்ப ETH ஐத் திரும்பப் பெற WETH வில்லைகளை எரிக்கவும்)
நீர்மைத்தன்மையைச் சேர்த்தல்
அழைப்பாளர்
- நீர்மைத் தொகுப்பில் சேர்க்கப்பட வேண்டிய தொகைகளுக்கான அனுமதித்தொகையை periphery கணக்கிற்கு வழங்கவும்.
- periphery ஒப்பந்தத்தின்
addLiquidityசெயற்கூறுகளில் ஒன்றை அழைக்கவும்.
periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)
- தேவைப்பட்டால் புதிய ஜோடி பரிமாற்றத்தை உருவாக்கவும்
- ஏற்கனவே ஒரு ஜோடி பரிமாற்றம் இருந்தால், சேர்க்க வேண்டிய வில்லைகளின் அளவைக் கணக்கிடவும். இது இரண்டு வில்லைகளுக்கும் ஒரே மாதிரியான மதிப்பாக இருக்க வேண்டும், எனவே புதிய வில்லைகளுக்கும் இருக்கும் வில்லைகளுக்கும் அதே விகிதம் இருக்க வேண்டும்.
- தொகைகள் ஏற்றுக்கொள்ளத்தக்கவையா என்பதைச் சரிபார்க்கவும் (அழைப்பாளர்கள் ஒரு குறைந்தபட்ச தொகையைக் குறிப்பிடலாம், அதற்குக் கீழே அவர்கள் நீர்மைத்தன்மையைச் சேர்க்க விரும்ப மாட்டார்கள்)
- மைய ஒப்பந்தத்தை அழைக்கவும்.
மைய ஒப்பந்தத்தில் (UniswapV2Pair.sol)
- நீர்மை டோக்கன்களை அச்சிட்டு அவற்றை அழைப்பாளருக்கு அனுப்பவும்
- இருப்புத் தொகைகளைப் புதுப்பிக்க
_updateஐ அழைக்கவும்
நீர்மைத்தன்மையை நீக்குதல்
அழைப்பாளர்
- அடிப்படை வில்லைகளுக்குப் பதிலாக எரிக்கப்பட வேண்டிய நீர்மை டோக்கன்களின் அனுமதித்தொகையை periphery கணக்கிற்கு வழங்கவும்.
- periphery ஒப்பந்தத்தின்
removeLiquidityசெயற்கூறுகளில் ஒன்றை அழைக்கவும்.
periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)
- நீர்மை டோக்கன்களை ஜோடி பரிமாற்றத்திற்கு அனுப்பவும்
மைய ஒப்பந்தத்தில் (UniswapV2Pair.sol)
- எரிக்கப்பட்ட வில்லைகளின் விகிதத்தில் அடிப்படை வில்லைகளை இலக்கு முகவரிக்கு அனுப்பவும். எடுத்துக்காட்டாக, தொகுப்பில் 1000 A வில்லைகள், 500 B வில்லைகள் மற்றும் 90 நீர்மை டோக்கன்கள் இருந்தால், எரிக்க 9 வில்லைகளைப் பெற்றால், நாம் 10% நீர்மை டோக்கன்களை எரிக்கிறோம், எனவே பயனருக்கு 100 A வில்லைகளையும் 50 B வில்லைகளையும் திருப்பி அனுப்புகிறோம்.
- நீர்மை டோக்கன்களை எரிக்கவும்
- இருப்புத் தொகைகளைப் புதுப்பிக்க
_updateஐ அழைக்கவும்
முக்கிய ஒப்பந்தங்கள்
இவை நீர்மைத்தன்மையைக் கொண்டிருக்கும் பாதுகாப்பான ஒப்பந்தங்கள் ஆகும்.
UniswapV2Pair.sol
இந்த ஒப்பந்தம் (opens in a new tab) வில்லைகளைப் பரிமாற்றம் செய்யும் உண்மையான தொகுப்பைச் செயல்படுத்துகிறது. இதுவே யூனிஸ்வாப்-இன் முக்கிய செயல்பாடாகும்.
pragma solidity =0.5.16;
import './interfaces/IUniswapV2Pair.sol';
import './UniswapV2ERC20.sol';
import './libraries/Math.sol';
import './libraries/UQ112x112.sol';
import './interfaces/IERC20.sol';
import './interfaces/IUniswapV2Factory.sol';
import './interfaces/IUniswapV2Callee.sol';
ஒப்பந்தம் இவற்றைச் செயல்படுத்துவதாலோ (IUniswapV2Pair மற்றும் UniswapV2ERC20) அல்லது இவற்றைச் செயல்படுத்தும் ஒப்பந்தங்களை அழைப்பதாலோ, ஒப்பந்தம் தெரிந்துகொள்ள வேண்டிய அனைத்து இடைமுகங்களும் இவையே.
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
இந்த ஒப்பந்தம் UniswapV2ERC20 என்பதிலிருந்து மரபுரிமையாகப் பெறுகிறது, இது நீர்மை டோக்கன்களுக்கான ERC-20 செயல்பாடுகளை வழங்குகிறது.
using SafeMath for uint;
வழிதல் (overflows) மற்றும் குறைதல் (underflows) ஆகியவற்றைத் தவிர்க்க SafeMath நிரலகம் (opens in a new tab) பயன்படுத்தப்படுகிறது. இது முக்கியமானது, இல்லையெனில் ஒரு மதிப்பு -1 ஆக இருக்க வேண்டிய இடத்தில், அதற்குப் பதிலாக 2^256-1 ஆக இருக்கும் சூழ்நிலை ஏற்படலாம்.
using UQ112x112 for uint224;
தொகுப்பு ஒப்பந்தத்தில் உள்ள பல கணக்கீடுகளுக்குப் பின்னங்கள் தேவைப்படுகின்றன. இருப்பினும், EVM-ஆல் பின்னங்கள் ஆதரிக்கப்படுவதில்லை.
யூனிஸ்வாப் கண்டறிந்த தீர்வு என்னவென்றால், 224 பிட் மதிப்புகளைப் பயன்படுத்துவதாகும், இதில் முழு எண் பகுதிக்கு 112 பிட்களும், பின்னத்திற்கு 112 பிட்களும் பயன்படுத்தப்படுகின்றன. எனவே 1.0 என்பது 2^112 ஆகவும், 1.5 என்பது 2^112 + 2^111 ஆகவும் குறிக்கப்படுகிறது.
இந்த நிரலகம் பற்றிய கூடுதல் விவரங்கள் ஆவணத்தில் பின்னர் கிடைக்கின்றன.
மாறிகள் (Variables)
uint public constant MINIMUM_LIQUIDITY = 10**3;
பூஜ்ஜியத்தால் வகுக்கப்படும் நிகழ்வுகளைத் தவிர்க்க, எப்போதும் இருக்கும் (ஆனால் பூஜ்ஜியக் கணக்கிற்குச் சொந்தமான) குறைந்தபட்ச நீர்மை டோக்கன்கள் உள்ளன. அந்த எண் MINIMUM_LIQUIDITY, அதாவது ஆயிரம்.
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
இது ERC-20 பரிமாற்றச் செயல்பாட்டிற்கான ABI தேர்வி (selector) ஆகும். இரண்டு வில்லைக் கணக்குகளில் ERC-20 வில்லைகளைப் பரிமாற்றம் செய்ய இது பயன்படுத்தப்படுகிறது.
address public factory;
இந்தத் தொகுப்பை உருவாக்கிய தொழிற்சாலை (factory) ஒப்பந்தம் இதுவாகும். ஒவ்வொரு தொகுப்பும் இரண்டு ERC-20 வில்லைகளுக்கு இடையிலான பரிமாற்றமாகும், தொழிற்சாலை என்பது இந்தத் தொகுப்புகள் அனைத்தையும் இணைக்கும் ஒரு மையப் புள்ளியாகும்.
address public token0;
address public token1;
இந்தத் தொகுப்பால் பரிமாற்றம் செய்யக்கூடிய இரண்டு வகையான ERC-20 வில்லைகளுக்கான ஒப்பந்தங்களின் முகவரிகள் உள்ளன.
uint112 private reserve0; // ஒற்றை சேமிப்பக இடத்தைப் பயன்படுத்துகிறது, getReserves மூலம் அணுகலாம்
uint112 private reserve1; // ஒற்றை சேமிப்பக இடத்தைப் பயன்படுத்துகிறது, getReserves மூலம் அணுகலாம்
ஒவ்வொரு வில்லை வகைக்கும் தொகுப்பு வைத்துள்ள இருப்புகள் (reserves). இரண்டும் ஒரே அளவிலான மதிப்பைக் குறிக்கின்றன என்று நாங்கள் கருதுகிறோம், எனவே ஒவ்வொரு token0-இன் மதிப்பும் reserve1/reserve0 token1-களுக்குச் சமமாகும்.
uint32 private blockTimestampLast; // ஒற்றை சேமிப்பக இடத்தைப் பயன்படுத்துகிறது, getReserves மூலம் அணுகலாம்
பரிமாற்றம் நிகழ்ந்த கடைசித் தொகுதிக்கான (block) நேரமுத்திரை (timestamp), காலப்போக்கில் பரிமாற்ற விகிதங்களைக் கண்காணிக்கப் பயன்படுத்தப்படுகிறது.
எத்திரியம் ஒப்பந்தங்களின் மிகப்பெரிய எரிவாயு செலவுகளில் ஒன்று சேமிப்பகமாகும், இது ஒப்பந்தத்தின் ஒரு அழைப்பிலிருந்து அடுத்த அழைப்பு வரை நிலைத்திருக்கும். ஒவ்வொரு சேமிப்பகக் கலமும் 256 பிட்கள் நீளமானது. எனவே reserve0, reserve1 மற்றும் blockTimestampLast ஆகிய மூன்று மாறிகளும், ஒரு ஒற்றைச் சேமிப்பக மதிப்பு மூன்றையும் உள்ளடக்கும் வகையில் ஒதுக்கப்படுகின்றன (112+112+32=256).
uint public price0CumulativeLast;
uint public price1CumulativeLast;
இந்த மாறிகள் ஒவ்வொரு வில்லைக்குமான ஒட்டுமொத்தச் செலவுகளைக் கொண்டுள்ளன (ஒவ்வொன்றும் மற்றொன்றின் அடிப்படையில்). ஒரு குறிப்பிட்ட காலப்பகுதியில் சராசரிப் பரிமாற்ற விகிதத்தைக் கணக்கிட இவற்றைப் பயன்படுத்தலாம்.
uint public kLast; // reserve0 * reserve1, மிகச் சமீபத்திய நீர்மைத்தன்மை நிகழ்வுக்குப் பிறகு உடனடியாக
token0 மற்றும் token1 ஆகியவற்றுக்கு இடையேயான பரிமாற்ற விகிதத்தை ஜோடிப் பரிமாற்றம் தீர்மானிக்கும் விதம் என்னவென்றால், வர்த்தகத்தின் போது இரண்டு இருப்புகளின் பெருக்கலை மாறிலியாக வைத்திருப்பதாகும். kLast என்பது இந்த மதிப்பாகும். ஒரு நீர்மை வழங்குபவர் வில்லைகளை டெபாசிட் செய்யும்போது அல்லது திரும்பப் பெறும்போது இது மாறுகிறது, மேலும் 0.3% சந்தைக் கட்டணம் காரணமாக இது சற்று அதிகரிக்கிறது.
இதோ ஒரு எளிய உதாரணம். எளிமைக்காக அட்டவணையில் தசமப் புள்ளிக்குப் பிறகு மூன்று இலக்கங்கள் மட்டுமே உள்ளன என்பதையும், 0.3% வர்த்தகக் கட்டணத்தை நாங்கள் புறக்கணிக்கிறோம் என்பதையும் நினைவில் கொள்க, எனவே எண்கள் துல்லியமாக இல்லை.
| நிகழ்வு | reserve0 | reserve1 | reserve0 * reserve1 | சராசரிப் பரிமாற்ற விகிதம் (token1 / token0) |
|---|---|---|---|---|
| ஆரம்ப அமைப்பு | 1,000.000 | 1,000.000 | 1,000,000 | |
| வர்த்தகர் A 50 token0-ஐ 47.619 token1-க்கு பரிமாற்றம் செய்கிறார் | 1,050.000 | 952.381 | 1,000,000 | 0.952 |
| வர்த்தகர் B 10 token0-ஐ 8.984 token1-க்கு பரிமாற்றம் செய்கிறார் | 1,060.000 | 943.396 | 1,000,000 | 0.898 |
| வர்த்தகர் C 40 token0-ஐ 34.305 token1-க்கு பரிமாற்றம் செய்கிறார் | 1,100.000 | 909.090 | 1,000,000 | 0.858 |
| வர்த்தகர் D 100 token1-ஐ 109.01 token0-க்கு பரிமாற்றம் செய்கிறார் | 990.990 | 1,009.090 | 1,000,000 | 0.917 |
| வர்த்தகர் E 10 token0-ஐ 10.079 token1-க்கு பரிமாற்றம் செய்கிறார் | 1,000.990 | 999.010 | 1,000,000 | 1.008 |
வர்த்தகர்கள் அதிக token0-ஐ வழங்குவதால், வழங்கல் மற்றும் தேவையின் அடிப்படையில் token1-இன் ஒப்பீட்டு மதிப்பு அதிகரிக்கிறது, மேலும் இது நேர்மாறாகவும் நிகழும்.
பூட்டு (Lock)
uint private unlocked = 1;
மறுநுழைவு துஷ்பிரயோகத்தை (reentrancy abuse) (opens in a new tab) அடிப்படையாகக் கொண்ட ஒரு வகை பாதுகாப்பு பாதிப்புகள் உள்ளன. யூனிஸ்வாப் தன்னிச்சையான ERC-20 வில்லைகளைப் பரிமாற்றம் செய்ய வேண்டும், அதாவது தங்களை அழைக்கும் யூனிஸ்வாப் சந்தையைத் தவறாகப் பயன்படுத்த முயற்சிக்கும் ERC-20 ஒப்பந்தங்களை அழைப்பதாகும்.
ஒப்பந்தத்தின் ஒரு பகுதியாக unlocked மாறியைக் கொண்டிருப்பதன் மூலம், செயல்பாடுகள் இயங்கிக் கொண்டிருக்கும்போதே (அதே பரிவர்த்தனைக்குள்) அவை அழைக்கப்படுவதைத் தடுக்கலாம்.
modifier lock() {
இந்தச் செயல்பாடு ஒரு மாற்றி (modifier) (opens in a new tab) ஆகும், இது ஒரு சாதாரணச் செயல்பாட்டைச் சுற்றி அதன் நடத்தையை ஏதேனும் ஒரு வகையில் மாற்றுவதற்கான ஒரு செயல்பாடாகும்.
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
unlocked ஒன்றுக்குச் சமமாக இருந்தால், அதை பூஜ்ஜியமாக அமைக்கவும். அது ஏற்கனவே பூஜ்ஜியமாக இருந்தால், அழைப்பை மீளமைத்து (revert), அதைத் தோல்வியடையச் செய்யவும்.
_;
ஒரு மாற்றியில் _; என்பது அசல் செயல்பாட்டு அழைப்பாகும் (அனைத்து அளவுருக்களுடனும்). இங்கே இதன் பொருள் என்னவென்றால், unlocked அழைக்கப்படும்போது ஒன்றாக இருந்தால் மட்டுமே செயல்பாட்டு அழைப்பு நிகழும், மேலும் அது இயங்கும்போது unlocked-இன் மதிப்பு பூஜ்ஜியமாக இருக்கும்.
unlocked = 1;
}
முக்கியச் செயல்பாடு திரும்பிய பிறகு, பூட்டை விடுவிக்கவும்.
இதர செயல்பாடுகள்
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
இந்தச் செயல்பாடு அழைப்பாளர்களுக்குப் பரிமாற்றத்தின் தற்போதைய நிலையை வழங்குகிறது. Solidity செயல்பாடுகள் பல மதிப்புகளை வழங்க முடியும் (opens in a new tab) என்பதைக் கவனிக்கவும்.
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
இந்த உள்ளகச் செயல்பாடு பரிமாற்றத்திலிருந்து வேறு ஒருவருக்கு ஒரு குறிப்பிட்ட அளவு ERC20 வில்லைகளைப் பரிமாற்றம் செய்கிறது. நாம் அழைக்கும் செயல்பாடு transfer(address,uint) என்பதை SELECTOR குறிப்பிடுகிறது (மேலே உள்ள வரையறையைப் பார்க்கவும்).
வில்லைச் செயல்பாட்டிற்கான இடைமுகத்தை இறக்குமதி செய்வதைத் தவிர்க்க, ABI செயல்பாடுகளில் (opens in a new tab) ஒன்றைப் பயன்படுத்தி அழைப்பை "கைமுறையாக" உருவாக்குகிறோம்.
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
ஒரு ERC-20 பரிமாற்ற அழைப்பு தோல்வியைப் புகாரளிக்க இரண்டு வழிகள் உள்ளன:
- மீளமை (Revert). வெளிப்புற ஒப்பந்தத்திற்கான அழைப்பு மீளமைக்கப்பட்டால், பூலியன் திரும்பப் பெறும் மதிப்பு
falseஆகும் - சாதாரணமாக முடிவடையும் ஆனால் தோல்வியைப் புகாரளிக்கும். அந்த நிலையில் திரும்பப் பெறும் மதிப்பு இடையகமானது (buffer) பூஜ்ஜியமற்ற நீளத்தைக் கொண்டிருக்கும், மேலும் அதை ஒரு பூலியன் மதிப்பாக டிகோட் செய்யும்போது அது
falseஆக இருக்கும்
இந்த நிபந்தனைகளில் ஏதேனும் ஒன்று நிகழ்ந்தால், மீளமைக்கவும்.
நிகழ்வுகள்
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
ஒரு நீர்மை வழங்குபவர் நீர்மைத்தன்மையை டெபாசிட் செய்யும்போது (Mint) அல்லது அதைத் திரும்பப் பெறும்போது (Burn) இந்த இரண்டு நிகழ்வுகளும் உமிழப்படுகின்றன. இரண்டு சந்தர்ப்பங்களிலும், டெபாசிட் செய்யப்பட்ட அல்லது திரும்பப் பெறப்பட்ட token0 மற்றும் token1-இன் அளவுகள் நிகழ்வின் ஒரு பகுதியாகும், அத்துடன் எங்களை அழைத்த கணக்கின் அடையாளமும் (sender) அடங்கும். திரும்பப் பெறும் நிலையில், வில்லைகளைப் பெற்ற இலக்கும் (to) நிகழ்வில் அடங்கும், இது அனுப்புநரைப் போலவே இருக்க வேண்டியதில்லை.
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
ஒரு வர்த்தகர் ஒரு வில்லைக்கு மற்றொன்றைப் பரிமாற்றம் செய்யும்போது இந்த நிகழ்வு உமிழப்படுகிறது. மீண்டும், அனுப்புநரும் சேருமிடமும் ஒன்றாக இருக்க வேண்டியதில்லை. ஒவ்வொரு வில்லையும் பரிமாற்றத்திற்கு அனுப்பப்படலாம் அல்லது அதிலிருந்து பெறப்படலாம்.
event Sync(uint112 reserve0, uint112 reserve1);
இறுதியாக, சமீபத்திய இருப்புத் தகவலை (அதனால் பரிமாற்ற விகிதத்தையும்) வழங்குவதற்காக, காரணம் எதுவாக இருந்தாலும், வில்லைகள் சேர்க்கப்படும்போதோ அல்லது திரும்பப் பெறப்படும்போதோ ஒவ்வொரு முறையும் Sync உமிழப்படுகிறது.
அமைப்புச் செயல்பாடுகள்
புதிய ஜோடிப் பரிமாற்றம் அமைக்கப்படும்போது இந்தச் செயல்பாடுகள் ஒருமுறை அழைக்கப்பட வேண்டும்.
constructor() public {
factory = msg.sender;
}
ஜோடியை உருவாக்கிய தொழிற்சாலையின் முகவரியை நாங்கள் கண்காணிப்போம் என்பதை ஆக்கி (constructor) உறுதி செய்கிறது. இந்தத் தகவல் initialize மற்றும் தொழிற்சாலைக் கட்டணத்திற்கும் (ஒன்று இருந்தால்) தேவைப்படுகிறது
// வரிசைப்படுத்தப்படும் நேரத்தில் தொழிற்சாலையால் ஒரு முறை அழைக்கப்படுகிறது
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // போதுமான சரிபார்ப்பு
token0 = _token0;
token1 = _token1;
}
இந்த ஜோடி பரிமாற்றம் செய்யும் இரண்டு ERC-20 வில்லைகளைக் குறிப்பிட தொழிற்சாலையை (மற்றும் தொழிற்சாலையை மட்டுமே) இந்தச் செயல்பாடு அனுமதிக்கிறது.
உள்ளகப் புதுப்பிப்புச் செயல்பாடுகள்
_update
// இருப்புக்களைப் புதுப்பிக்கவும், மேலும் ஒரு தொகுதிக்கு முதல் அழைப்பில், விலை திரட்டிகளைப் புதுப்பிக்கவும்
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
வில்லைகள் டெபாசிட் செய்யப்படும்போதோ அல்லது திரும்பப் பெறப்படும்போதோ ஒவ்வொரு முறையும் இந்தச் செயல்பாடு அழைக்கப்படுகிறது.
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
balance0 அல்லது balance1 (uint256) ஆனது uint112(-1) (=2^112-1) ஐ விட அதிகமாக இருந்தால் (எனவே இது uint112 ஆக மாற்றப்படும்போது வழிந்து 0-க்குத் திரும்பும்) வழிதல்களைத் தடுக்க _update-ஐத் தொடர மறுக்கவும். 10^18 அலகுகளாகப் பிரிக்கக்கூடிய ஒரு சாதாரண வில்லை மூலம், ஒவ்வொரு பரிமாற்றமும் ஒவ்வொரு வில்லைகளிலும் சுமார் 5.1*10^15 ஆக வரம்பிடப்பட்டுள்ளது என்று அர்த்தம். இதுவரை அது ஒரு பிரச்சனையாக இருந்ததில்லை.
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // வழிதல் விரும்பப்படுகிறது
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
கடந்த நேரம் பூஜ்ஜியமாக இல்லாவிட்டால், இந்தத் தொகுதியில் நாமே முதல் பரிமாற்றப் பரிவர்த்தனை என்று அர்த்தம். அந்த நிலையில், நாம் செலவுத் திரட்டிகளைப் (cost accumulators) புதுப்பிக்க வேண்டும்.
// * ஒருபோதும் வழியாது, மேலும் + வழிதல் விரும்பப்படுகிறது
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
ஒவ்வொரு செலவுத் திரட்டியும் சமீபத்திய செலவு (மற்ற வில்லையின் இருப்பு/இந்த வில்லையின் இருப்பு) மற்றும் கடந்த நேரத்தை விநாடிகளில் பெருக்கிப் புதுப்பிக்கப்படுகிறது. சராசரி விலையைப் பெற, நீங்கள் இரண்டு காலகட்டங்களில் உள்ள ஒட்டுமொத்த விலையைப் படித்து, அவற்றுக்கிடையேயான நேர வித்தியாசத்தால் வகுக்க வேண்டும். எடுத்துக்காட்டாக, இந்த நிகழ்வுகளின் வரிசையைக் கருதுங்கள்:
| நிகழ்வு | 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 ஆகும்.
இந்த விலைக் கணக்கீட்டின் காரணமாகவே பழைய இருப்பு அளவுகளை நாம் தெரிந்துகொள்ள வேண்டும்.
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
இறுதியாக, உலகளாவிய மாறிகளைப் புதுப்பித்து, ஒரு Sync நிகழ்வை உமிழவும்.
_mintFee
// கட்டணம் இயக்கப்பட்டிருந்தால், sqrt(k) இன் வளர்ச்சியில் 1/6 க்கு சமமான நீர்மைத்தன்மையை அச்சிடுங்கள்
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
யூனிஸ்வாப் 2.0-இல் வர்த்தகர்கள் சந்தையைப் பயன்படுத்த 0.30% கட்டணம் செலுத்துகிறார்கள். அந்தக் கட்டணத்தின் பெரும்பகுதி (வர்த்தகத்தில் 0.25%) எப்போதும் நீர்மை வழங்குபவர்களுக்குச் செல்கிறது. மீதமுள்ள 0.05% நீர்மை வழங்குபவர்களுக்கோ அல்லது தொழிற்சாலையால் நெறிமுறைக் கட்டணமாகக் குறிப்பிடப்பட்ட முகவரிக்கோ செல்லலாம், இது யூனிஸ்வாப்-இன் மேம்பாட்டு முயற்சிக்காக அவர்களுக்குச் செலுத்தப்படுகிறது.
கணக்கீடுகளைக் குறைக்க (அதனால் எரிவாயு செலவுகளையும் குறைக்க), ஒவ்வொரு பரிவர்த்தனையிலும் கணக்கிடப்படுவதற்குப் பதிலாக, தொகுப்பில் நீர்மைத்தன்மை சேர்க்கப்படும்போது அல்லது அகற்றப்படும்போது மட்டுமே இந்தக் கட்டணம் கணக்கிடப்படுகிறது.
address feeTo = IUniswapV2Factory(factory).feeTo();
feeOn = feeTo != address(0);
தொழிற்சாலையின் கட்டணச் சேருமிடத்தைப் படிக்கவும். அது பூஜ்ஜியமாக இருந்தால், நெறிமுறைக் கட்டணம் ஏதுமில்லை, அந்தக் கட்டணத்தைக் கணக்கிட வேண்டிய அவசியமும் இல்லை.
uint _kLast = kLast; // எரிவாயு சேமிப்பு
kLast நிலை மாறி சேமிப்பகத்தில் அமைந்துள்ளது, எனவே இது ஒப்பந்தத்திற்கான வெவ்வேறு அழைப்புகளுக்கு இடையே ஒரு மதிப்பைக் கொண்டிருக்கும்.
ஒப்பந்தத்திற்கான செயல்பாட்டு அழைப்பு முடிவடையும் போது வெளியிடப்படும் ஆவியாகும் நினைவகத்தை (volatile memory) அணுகுவதை விடச் சேமிப்பகத்தை அணுகுவது மிகவும் விலை உயர்ந்தது, எனவே எரிவாயுவைச் சேமிக்க நாம் ஒரு உள்ளக மாறியைப் பயன்படுத்துகிறோம்.
if (feeOn) {
if (_kLast != 0) {
நீர்மை வழங்குபவர்கள் தங்களின் நீர்மை டோக்கன்களின் மதிப்பீட்டின் மூலம் தங்களின் பங்கைப் பெறுகிறார்கள். ஆனால் நெறிமுறைக் கட்டணத்திற்குப் புதிய நீர்மை டோக்கன்கள் அச்சிடப்பட்டு feeTo முகவரிக்கு வழங்கப்பட வேண்டும்.
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
நெறிமுறைக் கட்டணத்தை வசூலிக்கப் புதிய நீர்மைத்தன்மை இருந்தால். வர்க்கமூலச் செயல்பாட்டை (square root function) இந்தக் கட்டுரையில் பின்னர் நீங்கள் காணலாம்
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
uint denominator = rootK.mul(5).add(rootKLast);
uint liquidity = numerator / denominator;
கட்டணங்களின் இந்தச் சிக்கலான கணக்கீடு வெள்ளை அறிக்கையின் (opens in a new tab) 5-ஆம் பக்கத்தில் விளக்கப்பட்டுள்ளது. kLast கணக்கிடப்பட்ட நேரத்திற்கும் தற்போதைய நேரத்திற்கும் இடையில் எந்த நீர்மைத்தன்மையும் சேர்க்கப்படவில்லை அல்லது அகற்றப்படவில்லை என்பதை நாம் அறிவோம் (ஏனெனில் நீர்மைத்தன்மை சேர்க்கப்படும்போதோ அல்லது அகற்றப்படும்போதோ, அது உண்மையில் மாறுவதற்கு முன்பு ஒவ்வொரு முறையும் இந்தக் கணக்கீட்டை இயக்குகிறோம்), எனவே reserve0 * reserve1-இல் ஏற்படும் எந்தவொரு மாற்றமும் பரிவர்த்தனைக் கட்டணங்களிலிருந்து வர வேண்டும் (அவை இல்லாமல் நாம் reserve0 * reserve1-ஐ மாறிலியாக வைத்திருப்போம்).
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
கூடுதல் நீர்மை டோக்கன்களை உருவாக்கி அவற்றை feeTo-க்கு ஒதுக்க UniswapV2ERC20._mint செயல்பாட்டைப் பயன்படுத்தவும்.
} else if (_kLast != 0) {
kLast = 0;
}
}
கட்டணம் ஏதுமில்லை என்றால் kLast-ஐ பூஜ்ஜியமாக அமைக்கவும் (அது ஏற்கனவே அப்படி இல்லையென்றால்). இந்த ஒப்பந்தம் எழுதப்பட்டபோது, ஒப்பந்தங்களுக்குத் தேவையில்லாத சேமிப்பகத்தைப் பூஜ்ஜியமாக்குவதன் மூலம் எத்திரியம் நிலையின் ஒட்டுமொத்த அளவைக் குறைக்க ஊக்குவிக்கும் ஒரு எரிவாயு பணத்தைத் திரும்பப்பெறும் அம்சம் (gas refund feature) (opens in a new tab) இருந்தது.
இந்தக் குறியீடு சாத்தியமானபோது அந்தப் பணத்தைத் திரும்பப் பெறுகிறது.
வெளிப்புறமாக அணுகக்கூடிய செயல்பாடுகள்
எந்தவொரு பரிவர்த்தனையோ அல்லது ஒப்பந்தமோ இந்தச் செயல்பாடுகளை அழைக்க முடியும் என்றாலும், அவை விளிம்பு (periphery) ஒப்பந்தத்திலிருந்து அழைக்கப்படும் வகையில் வடிவமைக்கப்பட்டுள்ளன என்பதைக் கவனத்தில் கொள்க. நீங்கள் அவற்றை நேரடியாக அழைத்தால், ஜோடிப் பரிமாற்றத்தை உங்களால் ஏமாற்ற முடியாது, ஆனால் ஒரு தவறு மூலம் நீங்கள் மதிப்பை இழக்க நேரிடலாம்.
mint
// முக்கியமான பாதுகாப்புச் சரிபார்ப்புகளைச் செய்யும் ஒரு ஒப்பந்தத்திலிருந்து இந்த குறைந்த-நிலை செயல்பாடு அழைக்கப்பட வேண்டும்
function mint(address to) external lock returns (uint liquidity) {
ஒரு நீர்மை வழங்குபவர் தொகுப்பில் நீர்மைத்தன்மையைச் சேர்க்கும்போது இந்தச் செயல்பாடு அழைக்கப்படுகிறது. இது வெகுமதியாகக் கூடுதல் நீர்மை டோக்கன்களை அச்சிடுகிறது. அதே பரிவர்த்தனையில் நீர்மைத்தன்மையைச் சேர்த்த பிறகு அதை அழைக்கும் ஒரு விளிம்பு ஒப்பந்தத்திலிருந்து இது அழைக்கப்பட வேண்டும் (எனவே முறையான உரிமையாளருக்கு முன்பு புதிய நீர்மைத்தன்மையைக் கோரும் பரிவர்த்தனையை வேறு எவராலும் சமர்ப்பிக்க முடியாது).
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // எரிவாயு சேமிப்பு
பல மதிப்புகளை வழங்கும் Solidity செயல்பாட்டின் முடிவுகளைப் படிக்கும் முறை இதுவாகும். கடைசியாகத் திரும்பப் பெற்ற மதிப்புகளான தொகுதி நேரமுத்திரையை நாங்கள் நிராகரிக்கிறோம், ஏனெனில் அது எங்களுக்குத் தேவையில்லை.
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
தற்போதைய நிலுவைகளைப் பெற்று, ஒவ்வொரு வில்லை வகையிலும் எவ்வளவு சேர்க்கப்பட்டுள்ளது என்பதைப் பார்க்கவும்.
bool feeOn = _mintFee(_reserve0, _reserve1);
வசூலிக்க வேண்டிய நெறிமுறைக் கட்டணங்கள் ஏதேனும் இருந்தால் அவற்றைக் கணக்கிட்டு, அதற்கேற்ப நீர்மை டோக்கன்களை அச்சிடவும். _mintFee-க்கான அளவுருக்கள் பழைய இருப்பு மதிப்புகள் என்பதால், கட்டணங்கள் காரணமாக ஏற்படும் தொகுப்பு மாற்றங்களின் அடிப்படையில் மட்டுமே கட்டணம் துல்லியமாகக் கணக்கிடப்படுகிறது.
uint _totalSupply = totalSupply; // எரிவாயு சேமிப்பு, _mintFee இல் totalSupply புதுப்பிக்கப்படலாம் என்பதால் இங்கே வரையறுக்கப்பட வேண்டும்
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
_mint(address(0), MINIMUM_LIQUIDITY); // முதல் MINIMUM_LIQUIDITY வில்லைகளை நிரந்தரமாகப் பூட்டவும்
இது முதல் டெபாசிட் என்றால், MINIMUM_LIQUIDITY வில்லைகளை உருவாக்கி, அவற்றைப் பூட்டுவதற்குப் பூஜ்ஜிய முகவரிக்கு அனுப்பவும். அவற்றை ஒருபோதும் மீட்க முடியாது, அதாவது தொகுப்பு ஒருபோதும் முழுமையாகக் காலியாகாது (இது சில இடங்களில் பூஜ்ஜியத்தால் வகுக்கப்படுவதிலிருந்து நம்மைக் காப்பாற்றுகிறது). MINIMUM_LIQUIDITY-இன் மதிப்பு ஆயிரம் ஆகும், பெரும்பாலான ERC-20-கள் ஒரு வில்லையின் 10^-18 அலகுகளாகப் பிரிக்கப்படுகின்றன என்பதைக் கருத்தில் கொண்டால், ETH ஆனது Wei ஆகப் பிரிக்கப்படுவது போல, இது ஒரு ஒற்றை வில்லையின் மதிப்பிற்கு 10^-15 ஆகும். இது அதிகச் செலவு அல்ல.
முதல் டெபாசிட் நேரத்தில் இரண்டு வில்லைகளின் ஒப்பீட்டு மதிப்பு எங்களுக்குத் தெரியாது, எனவே டெபாசிட் இரண்டு வில்லைகளிலும் சமமான மதிப்பை எங்களுக்கு வழங்குகிறது என்று கருதி, நாங்கள் தொகைகளைப் பெருக்கி ஒரு வர்க்கமூலத்தை எடுக்கிறோம்.
ஆர்பிட்ரேஜ் (arbitrage) மூலம் மதிப்பை இழப்பதைத் தவிர்க்க, சமமான மதிப்பை வழங்குவது டெபாசிட் செய்பவரின் நலனுக்காக இருப்பதால் இதை நாம் நம்பலாம். இரண்டு வில்லைகளின் மதிப்பும் ஒன்றுதான் என்று வைத்துக்கொள்வோம், ஆனால் எங்கள் டெபாசிட் செய்பவர் Token0-ஐ விட நான்கு மடங்கு அதிகமாக Token1-ஐ டெபாசிட் செய்துள்ளார். ஜோடிப் பரிமாற்றம் Token0-ஐ அதிக மதிப்புமிக்கதாகக் கருதுகிறது என்ற உண்மையைப் பயன்படுத்தி ஒரு வர்த்தகர் அதிலிருந்து மதிப்பை எடுக்க முடியும்.
| நிகழ்வு | reserve0 | reserve1 | reserve0 * reserve1 | தொகுப்பின் மதிப்பு (reserve0 + reserve1) |
|---|---|---|---|---|
| ஆரம்ப அமைப்பு | 8 | 32 | 256 | 40 |
| வர்த்தகர் 8 Token0 வில்லைகளை டெபாசிட் செய்து, 16 Token1-ஐத் திரும்பப் பெறுகிறார் | 16 | 16 | 256 | 32 |
நீங்கள் பார்ப்பது போல், வர்த்தகர் கூடுதலாக 8 வில்லைகளைச் சம்பாதித்துள்ளார், இது தொகுப்பின் மதிப்பு குறைவதிலிருந்து வருகிறது, இது அதை வைத்திருக்கும் டெபாசிட் செய்பவரைப் பாதிக்கிறது.
} else {
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 |
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity);
கூடுதல் நீர்மை டோக்கன்களை உருவாக்கி அவற்றைச் சரியான கணக்கிற்கு வழங்க UniswapV2ERC20._mint செயல்பாட்டைப் பயன்படுத்தவும்.
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 மற்றும் reserve1 புதுப்பித்த நிலையில் உள்ளன
emit Mint(msg.sender, amount0, amount1);
}
நிலை மாறிகளைப் புதுப்பித்து (reserve0, reserve1, மற்றும் தேவைப்பட்டால் kLast) பொருத்தமான நிகழ்வை உமிழவும்.
burn
// முக்கியமான பாதுகாப்புச் சரிபார்ப்புகளைச் செய்யும் ஒரு ஒப்பந்தத்திலிருந்து இந்த குறைந்த-நிலை செயல்பாடு அழைக்கப்பட வேண்டும்
function burn(address to) external lock returns (uint amount0, uint amount1) {
நீர்மைத்தன்மை திரும்பப் பெறப்படும்போது மற்றும் பொருத்தமான நீர்மை டோக்கன்கள் எரிக்கப்பட வேண்டியிருக்கும் போது இந்தச் செயல்பாடு அழைக்கப்படுகிறது. இது ஒரு விளிம்புக் கணக்கிலிருந்தும் அழைக்கப்பட வேண்டும்.
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // எரிவாயு சேமிப்பு
address _token0 = token0; // எரிவாயு சேமிப்பு
address _token1 = token1; // எரிவாயு சேமிப்பு
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
அழைப்பிற்கு முன்பு எரிக்கப்பட வேண்டிய நீர்மைத்தன்மையை விளிம்பு ஒப்பந்தம் இந்த ஒப்பந்தத்திற்கு மாற்றியது. அந்த வகையில் எவ்வளவு நீர்மைத்தன்மையை எரிக்க வேண்டும் என்பதை நாம் அறிவோம், மேலும் அது எரிக்கப்படுவதை நாம் உறுதிசெய்ய முடியும்.
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // எரிவாயு சேமிப்பு, _mintFee இல் totalSupply புதுப்பிக்கப்படலாம் என்பதால் இங்கே வரையறுக்கப்பட வேண்டும்
amount0 = liquidity.mul(balance0) / _totalSupply; // நிலுவைகளைப் பயன்படுத்துவது விகிதாசார விநியோகத்தை உறுதி செய்கிறது
amount1 = liquidity.mul(balance1) / _totalSupply; // நிலுவைகளைப் பயன்படுத்துவது விகிதாசார விநியோகத்தை உறுதி செய்கிறது
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
நீர்மை வழங்குபவர் இரண்டு வில்லைகளின் சமமான மதிப்பைப் பெறுகிறார். இந்த வழியில் நாம் பரிமாற்ற விகிதத்தை மாற்றுவதில்லை.
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 மற்றும் reserve1 புதுப்பித்த நிலையில் உள்ளன
emit Burn(msg.sender, amount0, amount1, to);
}
மீதமுள்ள burn செயல்பாடு மேலே உள்ள mint செயல்பாட்டின் கண்ணாடிப் பிம்பமாகும்.
swap
// முக்கியமான பாதுகாப்புச் சரிபார்ப்புகளைச் செய்யும் ஒரு ஒப்பந்தத்திலிருந்து இந்த குறைந்த-நிலை செயல்பாடு அழைக்கப்பட வேண்டும்
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
இந்தச் செயல்பாடும் ஒரு விளிம்பு ஒப்பந்தத்திலிருந்து அழைக்கப்பட வேண்டும்.
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // எரிவாயு சேமிப்பு
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // _token{0,1} க்கான நோக்கம், அடுக்கு மிகவும் ஆழமான பிழைகளைத் தவிர்க்கிறது
உள்ளூர் மாறிகளை நினைவகத்திலோ அல்லது அவை அதிகமாக இல்லாவிட்டால் நேரடியாக அடுக்கிலோ (stack) சேமிக்கலாம். நாம் அடுக்கைப் பயன்படுத்தும் வகையில் எண்ணிக்கையை வரம்பிட முடிந்தால், நாம் குறைந்த எரிவாயுவைப் பயன்படுத்துவோம். மேலும் விவரங்களுக்கு மஞ்சள் அறிக்கை, முறையான எத்திரியம் விவரக்குறிப்புகளைப் (opens in a new tab) பார்க்கவும், ப. 26, சமன்பாடு 298.
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // நம்பிக்கையுடன் வில்லைகளைப் பரிமாற்றவும்
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // நம்பிக்கையுடன் வில்லைகளைப் பரிமாற்றவும்
இந்த இடமாற்றம் நம்பிக்கையானது, ஏனென்றால் அனைத்து நிபந்தனைகளும் பூர்த்தி செய்யப்பட்டுள்ளன என்பதை நாம் உறுதியாக நம்புவதற்கு முன்பே நாம் இடமாற்றம் செய்கிறோம். எத்திரியத்தில் இது பரவாயில்லை, ஏனென்றால் அழைப்பில் பின்னர் நிபந்தனைகள் பூர்த்தி செய்யப்படாவிட்டால், அதிலிருந்தும் அது உருவாக்கிய எந்த மாற்றங்களிலிருந்தும் நாம் மீளமைக்கிறோம்.
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
கோரப்பட்டால் பரிமாற்றம் பற்றி பெறுநருக்குத் தெரிவிக்கவும்.
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
தற்போதைய நிலுவைகளைப் பெறவும். பரிமாற்றத்திற்காக எங்களை அழைப்பதற்கு முன்பு விளிம்பு ஒப்பந்தம் எங்களுக்கு வில்லைகளை அனுப்புகிறது. இது ஏமாற்றப்படவில்லை என்பதை ஒப்பந்தம் சரிபார்க்க எளிதாக்குகிறது, இந்தச் சரிபார்ப்பு முக்கிய ஒப்பந்தத்தில் நிகழ வேண்டும் (ஏனெனில் எங்கள் விளிம்பு ஒப்பந்தத்தைத் தவிர வேறு நிறுவனங்களாலும் நாம் அழைக்கப்படலாம்).
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // reserve{0,1}Adjusted க்கான நோக்கம், அடுக்கு மிகவும் ஆழமான பிழைகளைத் தவிர்க்கிறது
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
பரிமாற்றத்திலிருந்து நாம் இழக்கவில்லை என்பதை உறுதி செய்வதற்கான ஒரு பகுத்தறிவுச் சரிபார்ப்பு (sanity check) இதுவாகும். எந்தச் சூழ்நிலையிலும் ஒரு பரிமாற்றம் reserve0*reserve1-ஐக் குறைக்கக் கூடாது. பரிமாற்றத்தில் 0.3% கட்டணம் அனுப்பப்படுவதை நாம் உறுதி செய்யும் இடமும் இதுதான்; K-இன் மதிப்பைப் பகுத்தறிவுச் சரிபார்ப்பதற்கு முன்பு, நாம் இரண்டு நிலுவைகளையும் 1000-ஆல் பெருக்கி, தொகைகளை 3-ஆல் பெருக்கிக் கழிக்கிறோம், அதாவது அதன் K மதிப்பைத் தற்போதைய இருப்புகளின் K மதிப்புடன் ஒப்பிடுவதற்கு முன்பு நிலுவையிலிருந்து 0.3% (3/1000 = 0.003 = 0.3%) கழிக்கப்படுகிறது.
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
reserve0 மற்றும் reserve1-ஐப் புதுப்பிக்கவும், தேவைப்பட்டால் விலை திரட்டிகள் மற்றும் நேரமுத்திரையைப் புதுப்பித்து ஒரு நிகழ்வை உமிழவும்.
ஒத்திசைத்தல் அல்லது ஸ்கிம் (Sync or Skim)
ஜோடிப் பரிமாற்றம் தன்னிடம் இருப்பதாக நினைக்கும் இருப்புகளுடன் உண்மையான நிலுவைகள் ஒத்திசைக்கப்படாமல் போக வாய்ப்புள்ளது.
ஒப்பந்தத்தின் அனுமதியின்றி வில்லைகளைத் திரும்பப் பெற வழி இல்லை, ஆனால் டெபாசிட்டுகள் வேறு விஷயம். ஒரு கணக்கு mint அல்லது swap-ஐ அழைக்காமலேயே பரிமாற்றத்திற்கு வில்லைகளை மாற்ற முடியும்.
அந்த நிலையில் இரண்டு தீர்வுகள் உள்ளன:
sync, இருப்புகளைத் தற்போதைய நிலுவைகளுக்குப் புதுப்பிக்கவும்skim, கூடுதல் தொகையைத் திரும்பப் பெறவும். வில்லைகளை யார் டெபாசிட் செய்தார்கள் என்று எங்களுக்குத் தெரியாததால், எந்தவொரு கணக்கும்skim-ஐ அழைக்க அனுமதிக்கப்படுகிறது என்பதைக் கவனத்தில் கொள்க. இந்தத் தகவல் ஒரு நிகழ்வில் உமிழப்படுகிறது, ஆனால் நிகழ்வுகளைத் தொகுதிச்சங்கிலியிலிருந்து அணுக முடியாது.
// இருப்புக்களுடன் பொருந்துமாறு நிலுவைகளைக் கட்டாயப்படுத்தவும்
function skim(address to) external lock {
address _token0 = token0; // எரிவாயு சேமிப்பு
address _token1 = token1; // எரிவாயு சேமிப்பு
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}
// நிலுவைகளுடன் பொருந்துமாறு இருப்புக்களைக் கட்டாயப்படுத்தவும்
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}
UniswapV2Factory.sol
இந்த ஒப்பந்தம் (opens in a new tab) ஜோடிப் பரிமாற்றங்களை உருவாக்குகிறது.
pragma solidity =0.5.16;
import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';
contract UniswapV2Factory is IUniswapV2Factory {
address public feeTo;
address public feeToSetter;
நெறிமுறைக் கட்டணத்தைச் செயல்படுத்த இந்த நிலை மாறிகள் அவசியமாகும் (வெள்ளை அறிக்கையைப் (opens in a new tab) பார்க்கவும், ப. 5).
feeTo முகவரி நெறிமுறைக் கட்டணத்திற்கான நீர்மை டோக்கன்களைக் குவிக்கிறது, மேலும் feeToSetter என்பது feeTo-ஐ வேறு முகவரிக்கு மாற்ற அனுமதிக்கப்பட்ட முகவரியாகும்.
mapping(address => mapping(address => address)) public getPair;
address[] public allPairs;
இந்த மாறிகள் ஜோடிகளைக் கண்காணிக்கின்றன, அதாவது இரண்டு வில்லை வகைகளுக்கு இடையிலான பரிமாற்றங்கள்.
முதலாவது, getPair, அது பரிமாற்றம் செய்யும் இரண்டு ERC-20 வில்லைகளின் அடிப்படையில் ஒரு ஜோடிப் பரிமாற்ற ஒப்பந்தத்தை அடையாளம் காணும் ஒரு மேப்பிங் (mapping) ஆகும். ERC-20 வில்லைகள் அவற்றைச் செயல்படுத்தும் ஒப்பந்தங்களின் முகவரிகளால் அடையாளம் காணப்படுகின்றன, எனவே விசைகள் (keys) மற்றும் மதிப்பு அனைத்தும் முகவரிகளாகும். tokenA-லிருந்து tokenB-க்கு மாற்ற உங்களை அனுமதிக்கும் ஜோடிப் பரிமாற்றத்தின் முகவரியைப் பெற, நீங்கள் getPair[<tokenA address>][<tokenB address>]-ஐப் பயன்படுத்துகிறீர்கள் (அல்லது வேறு வழியில்).
இரண்டாவது மாறி, allPairs, இந்தத் தொழிற்சாலையால் உருவாக்கப்பட்ட ஜோடிப் பரிமாற்றங்களின் அனைத்து முகவரிகளையும் உள்ளடக்கிய ஒரு வரிசையாகும் (array). எத்திரியத்தில் நீங்கள் ஒரு மேப்பிங்கின் உள்ளடக்கத்தின் மீது மீண்டும் மீண்டும் செயல்பட முடியாது, அல்லது அனைத்து விசைகளின் பட்டியலையும் பெற முடியாது, எனவே இந்தத் தொழிற்சாலை எந்தப் பரிமாற்றங்களை நிர்வகிக்கிறது என்பதை அறிய இந்த மாறி மட்டுமே ஒரே வழியாகும்.
குறிப்பு: ஒரு மேப்பிங்கின் அனைத்து விசைகளின் மீதும் உங்களால் மீண்டும் மீண்டும் செயல்பட முடியாததற்குக் காரணம், ஒப்பந்தத் தரவுச் சேமிப்பகம் விலை உயர்ந்தது, எனவே நாம் அதை எவ்வளவு குறைவாகப் பயன்படுத்துகிறோமோ அவ்வளவு நல்லது, மேலும் அதை எவ்வளவு குறைவாக மாற்றுகிறோமோ அவ்வளவு நல்லது. நீங்கள் மறுசெய்கையை (iteration) ஆதரிக்கும் மேப்பிங்குகளை (opens in a new tab) உருவாக்கலாம், ஆனால் அவற்றுக்கு விசைகளின் பட்டியலுக்குக் கூடுதல் சேமிப்பகம் தேவைப்படும். பெரும்பாலான பயன்பாடுகளில் உங்களுக்கு அது தேவையில்லை.
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
புதிய ஜோடிப் பரிமாற்றம் உருவாக்கப்படும்போது இந்த நிகழ்வு உமிழப்படுகிறது. இதில் வில்லைகளின் முகவரிகள், ஜோடிப் பரிமாற்றத்தின் முகவரி மற்றும் தொழிற்சாலையால் நிர்வகிக்கப்படும் மொத்தப் பரிமாற்றங்களின் எண்ணிக்கை ஆகியவை அடங்கும்.
constructor(address _feeToSetter) public {
feeToSetter = _feeToSetter;
}
ஆக்கி செய்யும் ஒரே விஷயம் feeToSetter-ஐக் குறிப்பிடுவதுதான். தொழிற்சாலைகள் கட்டணமின்றித் தொடங்குகின்றன, மேலும் feeSetter மட்டுமே அதை மாற்ற முடியும்.
function allPairsLength() external view returns (uint) {
return allPairs.length;
}
இந்தச் செயல்பாடு பரிமாற்ற ஜோடிகளின் எண்ணிக்கையை வழங்குகிறது.
function createPair(address tokenA, address tokenB) external returns (address pair) {
இரண்டு ERC-20 வில்லைகளுக்கு இடையே ஒரு ஜோடிப் பரிமாற்றத்தை உருவாக்குவது தொழிற்சாலையின் முக்கியச் செயல்பாடாகும். இந்தச் செயல்பாட்டை யார் வேண்டுமானாலும் அழைக்கலாம் என்பதைக் கவனத்தில் கொள்க. புதிய ஜோடிப் பரிமாற்றத்தை உருவாக்க உங்களுக்கு யூனிஸ்வாப்-இடமிருந்து அனுமதி தேவையில்லை.
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
புதிய பரிமாற்றத்தின் முகவரி தீர்மானிக்கக்கூடியதாக இருக்க வேண்டும் என்று நாங்கள் விரும்புகிறோம், எனவே அதை முன்கூட்டியே புறச்சங்கிலியில் கணக்கிட முடியும் (இது அடுக்கு 2 (l2) பரிவர்த்தனைகளுக்குப் பயனுள்ளதாக இருக்கும்). இதைச் செய்ய, வில்லை முகவரிகளை நாம் எந்த வரிசையில் பெற்றிருந்தாலும், அவற்றின் நிலையான வரிசையை நாம் கொண்டிருக்க வேண்டும், எனவே அவற்றை இங்கே வரிசைப்படுத்துகிறோம்.
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // ஒற்றை சரிபார்ப்பு போதுமானது
சிறியவற்றை விடப் பெரிய நீர்மைத் தொகுப்புகள் சிறந்தவை, ஏனெனில் அவை நிலையான விலைகளைக் கொண்டுள்ளன. ஒரு ஜோடி வில்லைகளுக்கு ஒன்றுக்கு மேற்பட்ட நீர்மைத் தொகுப்புகளைக் கொண்டிருக்க நாங்கள் விரும்பவில்லை. ஏற்கனவே ஒரு பரிமாற்றம் இருந்தால், அதே ஜோடிக்கு இன்னொன்றை உருவாக்க வேண்டிய அவசியமில்லை.
bytes memory bytecode = type(UniswapV2Pair).creationCode;
புதிய ஒப்பந்தத்தை உருவாக்க, அதை உருவாக்கும் குறியீடு நமக்குத் தேவை (ஆக்கிச் செயல்பாடு மற்றும் உண்மையான ஒப்பந்தத்தின் EVM பைட் குறியீட்டை நினைவகத்தில் எழுதும் குறியீடு ஆகிய இரண்டும்). பொதுவாக Solidity-இல் நாம் addr = new <name of contract>(<constructor parameters>)-ஐப் பயன்படுத்துகிறோம், மேலும் கம்பைலர் (compiler) எல்லாவற்றையும் கவனித்துக்கொள்கிறது, ஆனால் ஒரு தீர்மானிக்கக்கூடிய ஒப்பந்த முகவரியைக் கொண்டிருக்க நாம் CREATE2 செயல்பாட்டுக் குறியீட்டைப் (opens in a new tab) பயன்படுத்த வேண்டும்.
இந்தக் குறியீடு எழுதப்பட்டபோது அந்தச் செயல்பாட்டுக் குறியீடு Solidity-ஆல் இன்னும் ஆதரிக்கப்படவில்லை, எனவே குறியீட்டைக் கைமுறையாகப் பெற வேண்டியிருந்தது. இது இனி ஒரு பிரச்சனையல்ல, ஏனெனில் Solidity இப்போது CREATE2-ஐ ஆதரிக்கிறது (opens in a new tab).
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
ஒரு செயல்பாட்டுக் குறியீடு Solidity-ஆல் இன்னும் ஆதரிக்கப்படாதபோது, இன்லைன் அசெம்பிளியைப் (inline assembly) (opens in a new tab) பயன்படுத்தி நாம் அதை அழைக்கலாம்.
IUniswapV2Pair(pair).initialize(token0, token1);
புதிய பரிமாற்றம் எந்த இரண்டு வில்லைகளைப் பரிமாற்றம் செய்கிறது என்பதைச் சொல்ல initialize செயல்பாட்டை அழைக்கவும்.
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // மேப்பிங்கை எதிர் திசையில் நிரப்பவும்
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
புதிய ஜோடித் தகவலை நிலை மாறிகளில் சேமித்து, புதிய ஜோடிப் பரிமாற்றத்தை உலகிற்குத் தெரிவிக்க ஒரு நிகழ்வை உமிழவும்.
function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeTo = _feeTo;
}
function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeToSetter = _feeToSetter;
}
}
இந்த இரண்டு செயல்பாடுகளும் கட்டணம் பெறுநரை (ஏதேனும் இருந்தால்) கட்டுப்படுத்தவும், feeSetter-ஐப் புதிய முகவரிக்கு மாற்றவும் feeSetter-ஐ அனுமதிக்கின்றன.
UniswapV2ERC20.sol
இந்த ஒப்பந்தம் (opens in a new tab) ERC-20 நீர்மை டோக்கனைச் செயல்படுத்துகிறது. இது ஓப்பன்செப்பெலின் ERC-20 ஒப்பந்தத்தைப் போன்றது, எனவே வேறுபட்ட பகுதியான permit செயல்பாட்டை மட்டுமே நான் விளக்குவேன்.
எத்திரியத்தில் பரிவர்த்தனைகளுக்கு ஈதர் (ETH) செலவாகும், இது உண்மையான பணத்திற்குச் சமமானதாகும். உங்களிடம் ERC-20 வில்லைகள் இருந்து ETH இல்லையென்றால், உங்களால் பரிவர்த்தனைகளை அனுப்ப முடியாது, எனவே உங்களால் அவற்றை வைத்து எதுவும் செய்ய முடியாது. இந்தப் பிரச்சனையைத் தவிர்ப்பதற்கான ஒரு தீர்வு மெட்டா-பரிவர்த்தனைகள் (meta-transactions) (opens in a new tab) ஆகும். வில்லைகளின் உரிமையாளர் வேறொருவர் வில்லைகளைப் புறச்சங்கிலியில் திரும்பப் பெற அனுமதிக்கும் ஒரு பரிவர்த்தனையில் கையொப்பமிட்டு, இணையத்தைப் பயன்படுத்தி அதைப் பெறுநருக்கு அனுப்புகிறார். ETH-ஐக் கொண்டுள்ள பெறுநர், உரிமையாளரின் சார்பாக அனுமதியைச் சமர்ப்பிக்கிறார்.
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
இந்த ஹாஷ் பரிவர்த்தனை வகைக்கான அடையாளங்காட்டி (opens in a new tab) ஆகும். இந்த அளவுருக்களுடன் Permit-ஐ மட்டுமே நாங்கள் இங்கே ஆதரிக்கிறோம்.
mapping(address => uint) public nonces;
ஒரு பெறுநர் டிஜிட்டல் கையொப்பத்தைப் போலியாக உருவாக்குவது சாத்தியமில்லை. இருப்பினும், அதே பரிவர்த்தனையை இரண்டு முறை அனுப்புவது எளிதானது (இது ஒரு வகையான மறுஇயக்கத் தாக்குதல் (replay attack) (opens in a new tab) ஆகும்). இதைத் தடுக்க, நாங்கள் ஒரு நான்ஸ் (opens in a new tab)-ஐப் பயன்படுத்துகிறோம். புதிய Permit-இன் நான்ஸ் கடைசியாகப் பயன்படுத்தப்பட்டதை விட ஒன்று அதிகமாக இல்லாவிட்டால், அது செல்லுபடியாகாது என்று நாங்கள் கருதுகிறோம்.
constructor() public {
uint chainId;
assembly {
chainId := chainid
}
சங்கிலி அடையாளங்காட்டியைக் (opens in a new tab) மீட்டெடுப்பதற்கான குறியீடு இதுவாகும். இது Yul (opens in a new tab) எனப்படும் EVM அசெம்பிளி பேச்சுவழக்கைப் பயன்படுத்துகிறது. Yul-இன் தற்போதைய பதிப்பில் நீங்கள் chainid-ஐ அல்ல, chainid()-ஐப் பயன்படுத்த வேண்டும் என்பதைக் கவனத்தில் கொள்க.
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
EIP-712-க்கான களப் பிரிப்பானைக் (domain separator) (opens in a new tab) கணக்கிடவும்.
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
அனுமதிகளைச் செயல்படுத்தும் செயல்பாடு இதுவாகும். இது தொடர்புடைய புலங்களையும், கையொப்பத்திற்கான (opens in a new tab) மூன்று அளவிடல் (scalar) மதிப்புகளையும் (v, r, மற்றும் s) அளவுருக்களாகப் பெறுகிறது.
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
கெடுவிற்குப் பிறகு பரிவர்த்தனைகளை ஏற்க வேண்டாம்.
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
abi.encodePacked(...) என்பது நாங்கள் பெற எதிர்பார்க்கும் செய்தி ஆகும். நான்ஸ் என்னவாக இருக்க வேண்டும் என்பதை நாங்கள் அறிவோம், எனவே அதை ஒரு அளவுருவாகப் பெற வேண்டிய அவசியமில்லை.
எத்திரியம் கையொப்ப அல்காரிதம் கையொப்பமிட 256 பிட்களைப் பெற எதிர்பார்க்கிறது, எனவே நாங்கள் keccak256 ஹாஷ் செயற்கூறைப் பயன்படுத்துகிறோம்.
address recoveredAddress = ecrecover(digest, v, r, s);
சுருக்கம் (digest) மற்றும் கையொப்பத்திலிருந்து ecrecover (opens in a new tab)-ஐப் பயன்படுத்தி அதில் கையொப்பமிட்ட முகவரியை நாம் பெறலாம்.
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
எல்லாம் சரியாக இருந்தால், இதை ஒரு ERC-20 ஒப்புதல் அளிப்பதாகக் (opens in a new tab) கருதவும்.
விளிம்பு ஒப்பந்தங்கள்
விளிம்பு ஒப்பந்தங்கள் யூனிஸ்வாப்பிற்கான (Uniswap) 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) நீங்கள் காணலாம்.
pragma solidity =0.6.6;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
import './interfaces/IUniswapV2Router02.sol';
import './libraries/UniswapV2Library.sol';
import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IWETH.sol';
இவற்றில் பெரும்பாலானவற்றை நாம் முன்பே சந்தித்திருக்கிறோம், அல்லது அவை மிகவும் வெளிப்படையானவை. இதற்கு ஒரே விதிவிலக்கு IWETH.sol ஆகும். யூனிஸ்வாப் v2 எந்தவொரு ERC-20 வில்லைகளின் ஜோடிக்கும் பரிமாற்றங்களை அனுமதிக்கிறது, ஆனால் ஈதர் (ETH) ஒரு ERC-20 வில்லை அல்ல. இது தரநிலைக்கு முந்தையது மற்றும் தனித்துவமான வழிமுறைகளால் பரிமாற்றம் செய்யப்படுகிறது. ERC-20 வில்லைகளுக்குப் பொருந்தும் ஒப்பந்தங்களில் ETH-இன் பயன்பாட்டைச் செயல்படுத்த, மக்கள் பொதியப்பட்ட ஈதர் (WETH) (opens in a new tab) ஒப்பந்தத்தை உருவாக்கினர். நீங்கள் இந்த ஒப்பந்தத்திற்கு ETH-ஐ அனுப்புகிறீர்கள், அது உங்களுக்கு சமமான அளவு WETH-ஐ அச்சிடுகிறது. அல்லது நீங்கள் WETH-ஐ எரித்து, ETH-ஐ திரும்பப் பெறலாம்.
contract UniswapV2Router02 is IUniswapV2Router02 {
using SafeMath for uint;
address public immutable override factory;
address public immutable override WETH;
திசைவி எந்த தொழிற்சாலையைப் பயன்படுத்த வேண்டும் என்பதையும், WETH தேவைப்படும் பரிவர்த்தனைகளுக்கு எந்த WETH ஒப்பந்தத்தைப் பயன்படுத்த வேண்டும் என்பதையும் அறிந்திருக்க வேண்டும். இந்த மதிப்புகள் மாற்றவியலாதவை (opens in a new tab), அதாவது அவற்றை ஆக்கியில் மட்டுமே அமைக்க முடியும். குறைந்த நேர்மையான ஒப்பந்தங்களைச் சுட்டிக்காட்டும் வகையில் அவற்றை யாராலும் மாற்ற முடியாது என்ற நம்பிக்கையை இது பயனர்களுக்கு அளிக்கிறது.
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
இந்த மாற்றியமைப்பான் நேர வரம்புக்குட்பட்ட பரிவர்த்தனைகள் ("உங்களால் முடிந்தால் Y நேரத்திற்கு முன் X-ஐச் செய்யுங்கள்") அவற்றின் நேர வரம்பிற்குப் பிறகு நடக்காது என்பதை உறுதி செய்கிறது.
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
ஆக்கி மாற்றவியலாத நிலை மாறிகளை மட்டுமே அமைக்கிறது.
receive() external payable {
assert(msg.sender == WETH); // WETH ஒப்பந்தத்திலிருந்து ஃபால்பேக் மூலம் மட்டுமே ETH ஐ ஏற்கவும்
}
WETH ஒப்பந்தத்திலிருந்து வில்லைகளை மீண்டும் ETH ஆக மீட்கும்போது இந்தச் செயற்கூறு அழைக்கப்படுகிறது. நாம் பயன்படுத்தும் WETH ஒப்பந்தத்திற்கு மட்டுமே அதைச் செய்ய அங்கீகாரம் உள்ளது.
நீர்மைத்தன்மையைச் சேர்
இந்தச் செயற்கூறுகள் ஜோடி பரிமாற்றத்தில் வில்லைகளைச் சேர்க்கின்றன, இது நீர்மைத் தொகுப்பை அதிகரிக்கிறது.
// **** நீர்மைத்தன்மையைச் சேர் ****
function _addLiquidity(
ஜோடி பரிமாற்றத்தில் டெபாசிட் செய்யப்பட வேண்டிய A மற்றும் B வில்லைகளின் அளவைக் கணக்கிட இந்தச் செயற்கூறு பயன்படுத்தப்படுகிறது.
address tokenA,
address tokenB,
இவை ERC-20 வில்லை ஒப்பந்தங்களின் முகவரிகள்.
uint amountADesired,
uint amountBDesired,
இவை நீர்மை வழங்குபவர் டெபாசிட் செய்ய விரும்பும் தொகைகள். அவை டெபாசிட் செய்யப்பட வேண்டிய A மற்றும் B-இன் அதிகபட்ச அளவுகளாகும்.
uint amountAMin,
uint amountBMin
இவை டெபாசிட் செய்ய ஏற்றுக்கொள்ளக்கூடிய குறைந்தபட்ச அளவுகள். இந்த அளவுகள் அல்லது அதற்கு மேற்பட்டவற்றுடன் பரிவர்த்தனை நடைபெற முடியாவிட்டால், அதிலிருந்து மீளமைக்கவும். இந்த அம்சம் உங்களுக்குத் தேவையில்லை என்றால், பூஜ்ஜியத்தைக் குறிப்பிடவும்.
நீர்மை வழங்குபவர்கள் பொதுவாக ஒரு குறைந்தபட்சத்தைக் குறிப்பிடுகிறார்கள், ஏனெனில் அவர்கள் பரிவர்த்தனையை தற்போதைய பரிமாற்ற விகிதத்திற்கு நெருக்கமானதாகக் கட்டுப்படுத்த விரும்புகிறார்கள். பரிமாற்ற விகிதம் அதிகமாக ஏற்ற இறக்கமாக இருந்தால், அது அடிப்படை மதிப்புகளை மாற்றும் செய்திகளைக் குறிக்கலாம், மேலும் அவர்கள் என்ன செய்ய வேண்டும் என்பதை கைமுறையாக முடிவு செய்ய விரும்புகிறார்கள்.
எடுத்துக்காட்டாக, பரிமாற்ற விகிதம் ஒன்றுக்கு ஒன்று என இருக்கும்போது, நீர்மை வழங்குபவர் இந்த மதிப்புகளைக் குறிப்பிடும் ஒரு வழக்கைக் கற்பனை செய்து பாருங்கள்:
| அளவுரு | மதிப்பு |
|---|---|
| amountADesired | 1000 |
| amountBDesired | 1000 |
| amountAMin | 900 |
| amountBMin | 800 |
பரிமாற்ற விகிதம் 0.9 மற்றும் 1.25-க்கு இடையில் இருக்கும் வரை, பரிவர்த்தனை நடைபெறும். பரிமாற்ற விகிதம் அந்த வரம்பிற்கு வெளியே சென்றால், பரிவர்த்தனை ரத்து செய்யப்படும்.
இந்த முன்னெச்சரிக்கைக்குக் காரணம், பரிவர்த்தனைகள் உடனடியானவை அல்ல, நீங்கள் அவற்றைச் சமர்ப்பிக்கிறீர்கள், இறுதியில் ஒரு சரிபார்ப்பான் அவற்றை ஒரு தொகுதியில் சேர்க்கப் போகிறார் (உங்கள் எரிவாயு விலை மிகக் குறைவாக இல்லாவிட்டால், அவ்வாறான நிலையில் அதை மேலெழுத அதே நான்ஸ் மற்றும் அதிக எரிவாயு விலையுடன் மற்றொரு பரிவர்த்தனையை நீங்கள் சமர்ப்பிக்க வேண்டும்). சமர்ப்பிப்பதற்கும் சேர்க்கப்படுவதற்கும் இடையிலான இடைவெளியில் என்ன நடக்கிறது என்பதை உங்களால் கட்டுப்படுத்த முடியாது.
) internal virtual returns (uint amountA, uint amountB) {
இருப்புக்களுக்கு இடையிலான தற்போதைய விகிதத்திற்கு சமமான விகிதத்தைக் கொண்டிருக்க நீர்மை வழங்குபவர் டெபாசிட் செய்ய வேண்டிய தொகைகளை இந்தச் செயற்கூறு வழங்குகிறது.
// ஜோடி இன்னும் இல்லை என்றால் அதை உருவாக்கவும்
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
இந்த வில்லை ஜோடிக்கு இன்னும் பரிமாற்றம் இல்லை என்றால், அதை உருவாக்கவும்.
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
ஜோடியில் உள்ள தற்போதைய இருப்புக்களைப் பெறவும்.
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
தற்போதைய இருப்புக்கள் காலியாக இருந்தால், இது ஒரு புதிய ஜோடி பரிமாற்றம் ஆகும். டெபாசிட் செய்யப்பட வேண்டிய தொகைகள் நீர்மை வழங்குபவர் வழங்க விரும்பும் தொகைகளுக்குச் சரியாகச் சமமாக இருக்க வேண்டும்.
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
தொகைகள் என்னவாக இருக்கும் என்பதை நாம் பார்க்க வேண்டுமானால், இந்தச் செயற்கூறைப் (opens in a new tab) பயன்படுத்தி உகந்த தொகையைப் பெறுகிறோம். தற்போதைய இருப்புக்களின் அதே விகிதத்தை நாங்கள் விரும்புகிறோம்.
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
(amountA, amountB) = (amountADesired, amountBOptimal);
amountBOptimal ஆனது நீர்மை வழங்குபவர் டெபாசிட் செய்ய விரும்பும் தொகையை விடச் சிறியதாக இருந்தால், நீர்மை டெபாசிட் செய்பவர் நினைப்பதை விட வில்லை B தற்போது அதிக மதிப்புடையது என்று அர்த்தம், எனவே சிறிய தொகை தேவைப்படுகிறது.
} else {
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
உகந்த B தொகை விரும்பிய B தொகையை விட அதிகமாக இருந்தால், நீர்மை டெபாசிட் செய்பவர் நினைப்பதை விட B வில்லைகள் தற்போது குறைவான மதிப்புடையவை என்று அர்த்தம், எனவே அதிக தொகை தேவைப்படுகிறது. இருப்பினும், விரும்பிய தொகை அதிகபட்சமாகும், எனவே எங்களால் அதைச் செய்ய முடியாது. அதற்குப் பதிலாக, விரும்பிய அளவு B வில்லைகளுக்கான உகந்த A வில்லைகளின் எண்ணிக்கையைக் கணக்கிடுகிறோம்.
இவை அனைத்தையும் ஒன்றாக இணைத்தால் இந்த வரைபடத்தைப் பெறுகிறோம். நீங்கள் ஆயிரம் A வில்லைகளையும் (நீலக் கோடு) ஆயிரம் B வில்லைகளையும் (சிவப்புக் கோடு) டெபாசிட் செய்ய முயற்சிக்கிறீர்கள் என்று வைத்துக்கொள்வோம். x அச்சு என்பது பரிமாற்ற விகிதம், A/B ஆகும். x=1 ஆக இருந்தால், அவை மதிப்பில் சமமாக இருக்கும், மேலும் நீங்கள் ஒவ்வொன்றிலும் ஆயிரத்தை டெபாசிட் செய்வீர்கள். x=2 ஆக இருந்தால், A ஆனது B-இன் மதிப்பை விட இரண்டு மடங்கு ஆகும் (ஒவ்வொரு A வில்லைக்கும் இரண்டு B வில்லைகளைப் பெறுவீர்கள்) எனவே நீங்கள் ஆயிரம் B வில்லைகளை டெபாசிட் செய்கிறீர்கள், ஆனால் 500 A வில்லைகளை மட்டுமே டெபாசிட் செய்கிறீர்கள். x=0.5 ஆக இருந்தால், நிலைமை தலைகீழாக மாறும், ஆயிரம் A வில்லைகள் மற்றும் ஐநூறு B வில்லைகள்.
நீங்கள் மைய ஒப்பந்தத்தில் நேரடியாக நீர்மைத்தன்மையை டெபாசிட் செய்யலாம் (UniswapV2Pair::mint (opens in a new tab)-ஐப் பயன்படுத்தி), ஆனால் மைய ஒப்பந்தம் அது ஏமாற்றப்படவில்லையா என்பதை மட்டுமே சரிபார்க்கிறது, எனவே உங்கள் பரிவர்த்தனையை நீங்கள் சமர்ப்பிக்கும் நேரத்திற்கும் அது செயல்படுத்தப்படும் நேரத்திற்கும் இடையில் பரிமாற்ற விகிதம் மாறினால் மதிப்பை இழக்கும் அபாயம் உங்களுக்கு உள்ளது. நீங்கள் விளிம்பு ஒப்பந்தத்தைப் பயன்படுத்தினால், நீங்கள் டெபாசிட் செய்ய வேண்டிய தொகையை அது கணக்கிட்டு உடனடியாக டெபாசிட் செய்கிறது, எனவே பரிமாற்ற விகிதம் மாறாது மற்றும் நீங்கள் எதையும் இழக்க மாட்டீர்கள்.
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
நீர்மைத்தன்மையை டெபாசிட் செய்ய ஒரு பரிவர்த்தனையால் இந்தச் செயற்கூறு அழைக்கப்படலாம். இரண்டு விதிவிலக்குகளுடன், பெரும்பாலான அளவுருக்கள் மேலே உள்ள _addLiquidity-இல் உள்ளதைப் போலவே இருக்கும்:
. to என்பது தொகுப்பில் நீர்மை வழங்குபவரின் பங்கைக் காட்ட அச்சிடப்பட்ட புதிய நீர்மை டோக்கன்களைப் பெறும் முகவரியாகும்
. deadline என்பது பரிவர்த்தனைக்கான நேர வரம்பாகும்
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
உண்மையில் டெபாசிட் செய்ய வேண்டிய தொகைகளை நாங்கள் கணக்கிட்டு, பின்னர் நீர்மைத் தொகுப்பின் முகவரியைக் கண்டறிகிறோம். எரிவாயுவைச் சேமிக்க, தொழிற்சாலையைக் கேட்பதன் மூலம் இதைச் செய்ய மாட்டோம், ஆனால் pairFor என்ற நிரலகச் செயற்கூறைப் பயன்படுத்துகிறோம் (கீழே நிரலகங்களில் பார்க்கவும்)
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
பயனரிடமிருந்து சரியான அளவு வில்லைகளை ஜோடி பரிமாற்றத்திற்குப் பரிமாற்றம் செய்யவும்.
liquidity = IUniswapV2Pair(pair).mint(to);
}
பதிலுக்கு, தொகுப்பின் பகுதி உரிமைக்காக to முகவரிக்கு நீர்மை டோக்கன்களை வழங்கவும். மைய ஒப்பந்தத்தின் mint செயற்கூறு தன்னிடம் எத்தனை கூடுதல் வில்லைகள் உள்ளன என்பதைப் பார்க்கிறது (கடைசியாக நீர்மைத்தன்மை மாறியபோது இருந்ததை ஒப்பிடுகையில்) அதற்கேற்ப நீர்மைத்தன்மையை அச்சிடுகிறது.
function addLiquidityETH(
address token,
uint amountTokenDesired,
ஒரு நீர்மை வழங்குபவர் வில்லை/ETH ஜோடி பரிமாற்றத்திற்கு நீர்மைத்தன்மையை வழங்க விரும்பும்போது, சில வேறுபாடுகள் உள்ளன. நீர்மை வழங்குபவருக்காக ETH-ஐப் பொதியிடுவதை ஒப்பந்தம் கையாளுகிறது. பயனர் எத்தனை ETH-ஐ டெபாசிட் செய்ய விரும்புகிறார் என்பதைக் குறிப்பிட வேண்டிய அவசியமில்லை, ஏனெனில் பயனர் அவற்றை பரிவர்த்தனையுடன் அனுப்புகிறார் (தொகை msg.value-இல் கிடைக்கிறது).
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
ETH-ஐ டெபாசிட் செய்ய, ஒப்பந்தம் முதலில் அதை WETH ஆகப் பொதியிடுகிறது, பின்னர் WETH-ஐ ஜோடிக்குப் பரிமாற்றம் செய்கிறது. பரிமாற்றம் ஒரு assert-இல் பொதியப்பட்டிருப்பதைக் கவனியுங்கள். இதன் பொருள் என்னவென்றால், பரிமாற்றம் தோல்வியுற்றால் இந்த ஒப்பந்த அழைப்பும் தோல்வியடையும், எனவே பொதியிடுதல் உண்மையில் நடக்காது.
liquidity = IUniswapV2Pair(pair).mint(to);
// ஏதேனும் இருந்தால், டஸ்ட் ஈதரைத் திரும்பப் பெறவும்
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
பயனர் ஏற்கனவே எங்களுக்கு ETH-ஐ அனுப்பியுள்ளார், எனவே ஏதேனும் கூடுதல் மீதம் இருந்தால் (பயனர் நினைத்ததை விட மற்ற வில்லை குறைவான மதிப்புடையது என்பதால்), நாங்கள் பணத்தைத் திரும்பப் பெற வேண்டும்.
நீர்மைத்தன்மையை அகற்று
இந்தச் செயற்கூறுகள் நீர்மைத்தன்மையை அகற்றி, நீர்மை வழங்குபவருக்குத் திருப்பிச் செலுத்தும்.
// **** நீர்மைத்தன்மையை அகற்று ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
நீர்மைத்தன்மையை அகற்றுவதற்கான எளிய வழக்கு. நீர்மை வழங்குபவர் ஏற்றுக்கொள்ள ஒப்புக்கொள்ளும் ஒவ்வொரு வில்லையின் குறைந்தபட்ச அளவு உள்ளது, மேலும் அது காலக்கெடுவிற்கு முன் நடக்க வேண்டும்.
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // ஜோடிக்கு நீர்மைத்தன்மையை அனுப்பவும்
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
மைய ஒப்பந்தத்தின் burn செயற்கூறு பயனருக்கு வில்லைகளைத் திருப்பிச் செலுத்துவதைக் கையாளுகிறது.
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
ஒரு செயற்கூறு பல மதிப்புகளை வழங்கும்போது, ஆனால் அவற்றில் சிலவற்றில் மட்டுமே நாம் ஆர்வமாக இருக்கும்போது, அந்த மதிப்புகளை மட்டுமே நாம் இவ்வாறு பெறுகிறோம். ஒரு மதிப்பைப் படித்து அதை ஒருபோதும் பயன்படுத்தாமல் இருப்பதை விட எரிவாயு அடிப்படையில் இது சற்றே மலிவானது.
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
மைய ஒப்பந்தம் அவற்றை வழங்கும் முறையிலிருந்து (குறைந்த முகவரி வில்லை முதலில்) பயனர் எதிர்பார்க்கும் முறைக்கு (tokenA மற்றும் tokenB-க்கு ஒத்ததாக) தொகைகளை மொழிபெயர்க்கவும்.
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
முதலில் பரிமாற்றத்தைச் செய்துவிட்டு, அது சட்டபூர்வமானதா என்பதைச் சரிபார்ப்பது சரிதான், ஏனென்றால் அது இல்லையென்றால், அனைத்து நிலை மாற்றங்களிலிருந்தும் நாம் மீளமைப்போம்.
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
ETH-க்கான நீர்மைத்தன்மையை அகற்றுவது கிட்டத்தட்ட ஒன்றே, தவிர நாம் WETH வில்லைகளைப் பெற்று, பின்னர் நீர்மை வழங்குபவருக்குத் திரும்பக் கொடுக்க அவற்றை ETH ஆக மீட்கிறோம்.
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
அனுமதி பொறிமுறையைப் பயன்படுத்தி, ஈதர் இல்லாத பயனர்கள் தொகுப்பிலிருந்து திரும்பப் பெற அனுமதிக்க இந்தச் செயற்கூறுகள் மெட்டா-பரிவர்த்தனைகளை அனுப்புகின்றன.
// **** நீர்மைத்தன்மையை அகற்று (பரிமாற்றக் கட்டண வில்லைகளை ஆதரிக்கிறது) ****
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
பரிமாற்றம் அல்லது சேமிப்புக் கட்டணங்களைக் கொண்ட வில்லைகளுக்கு இந்தச் செயற்கூறைப் பயன்படுத்தலாம். ஒரு வில்லைக்கு அத்தகைய கட்டணங்கள் இருக்கும்போது, வில்லையின் எவ்வளவு பகுதியை நாம் திரும்பப் பெறுகிறோம் என்பதைச் சொல்ல removeLiquidity செயற்கூறை நாம் நம்ப முடியாது, எனவே நாம் முதலில் திரும்பப் பெற வேண்டும், பின்னர் இருப்பைப் பெற வேண்டும்.
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}
இறுதிச் செயற்கூறு சேமிப்புக் கட்டணங்களை மெட்டா-பரிவர்த்தனைகளுடன் இணைக்கிறது.
வர்த்தகம்
// **** பரிமாற்றம் ****
// ஆரம்பத் தொகை ஏற்கனவே முதல் ஜோடிக்கு அனுப்பப்பட்டிருக்க வேண்டும்
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
வர்த்தகர்களுக்கு வெளிப்படுத்தப்படும் செயற்கூறுகளுக்குத் தேவையான உள் செயலாக்கத்தை இந்தச் செயற்கூறு செய்கிறது.
for (uint i; i < path.length - 1; i++) {
நான் இதை எழுதும்போது 388,160 ERC-20 வில்லைகள் (opens in a new tab) உள்ளன. ஒவ்வொரு வில்லை ஜோடிக்கும் ஒரு ஜோடி பரிமாற்றம் இருந்தால், அது 150 பில்லியனுக்கும் அதிகமான ஜோடி பரிமாற்றங்களாக இருக்கும். முழு சங்கிலியும், தற்போது, அந்தக் கணக்குகளின் எண்ணிக்கையில் 0.1% மட்டுமே கொண்டுள்ளது (opens in a new tab). அதற்குப் பதிலாக, பரிமாற்றச் செயற்கூறுகள் ஒரு பாதையின் கருத்தை ஆதரிக்கின்றன. ஒரு வர்த்தகர் A-ஐ B-க்கும், B-ஐ C-க்கும், மற்றும் C-ஐ D-க்கும் பரிமாறிக்கொள்ளலாம், எனவே நேரடி A-D ஜோடி பரிமாற்றம் தேவையில்லை.
இந்தச் சந்தைகளில் விலைகள் ஒத்திசைக்கப்படும் போக்கைக் கொண்டுள்ளன, ஏனெனில் அவை ஒத்திசைவில் இல்லாதபோது அது நடுவர் வாய்ப்பை உருவாக்குகிறது. எடுத்துக்காட்டாக, 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 |
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
நாம் தற்போது கையாளும் ஜோடியைப் பெறவும், அதை வரிசைப்படுத்தவும் (ஜோடியுடன் பயன்படுத்த) மற்றும் எதிர்பார்க்கப்படும் வெளியீட்டுத் தொகையைப் பெறவும்.
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
ஜோடி பரிமாற்றம் எதிர்பார்க்கும் வகையில் வரிசைப்படுத்தப்பட்ட, எதிர்பார்க்கப்படும் வெளியீட்டுத் தொகைகளைப் பெறவும்.
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
இது கடைசிப் பரிமாற்றமா? அப்படியானால், வர்த்தகத்திற்காகப் பெறப்பட்ட வில்லைகளை இலக்குக்கு அனுப்பவும். இல்லையென்றால், அதை அடுத்த ஜோடி பரிமாற்றத்திற்கு அனுப்பவும்.
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
வில்லைகளைப் பரிமாற்றம் செய்ய ஜோடி பரிமாற்றத்தை உண்மையில் அழைக்கவும். பரிமாற்றத்தைப் பற்றிச் சொல்ல எங்களுக்கு ஒரு திரும்ப அழைப்பு தேவையில்லை, எனவே அந்தப் புலத்தில் எந்த பைட்டுகளையும் நாங்கள் அனுப்ப மாட்டோம்.
function swapExactTokensForTokens(
ஒரு வில்லைக்கு மற்றொரு வில்லைப் பரிமாற்றம் செய்ய வர்த்தகர்களால் இந்தச் செயற்கூறு நேரடியாகப் பயன்படுத்தப்படுகிறது.
uint amountIn,
uint amountOutMin,
address[] calldata path,
இந்த அளவுரு ERC-20 ஒப்பந்தங்களின் முகவரிகளைக் கொண்டுள்ளது. மேலே விளக்கியபடி, இது ஒரு வரிசையாகும், ஏனெனில் உங்களிடம் உள்ள சொத்திலிருந்து நீங்கள் விரும்பும் சொத்தைப் பெற நீங்கள் பல ஜோடி பரிமாற்றங்கள் வழியாகச் செல்ல வேண்டியிருக்கும்.
Solidity-இல் ஒரு செயற்கூறு அளவுருவை memory அல்லது calldata-இல் சேமிக்கலாம். செயற்கூறு ஒப்பந்தத்திற்கான நுழைவுப் புள்ளியாக இருந்தால், பயனரிடமிருந்து (பரிவர்த்தனையைப் பயன்படுத்தி) அல்லது வேறு ஒப்பந்தத்திலிருந்து நேரடியாக அழைக்கப்பட்டால், அளவுருவின் மதிப்பை அழைப்புத் தரவிலிருந்து நேரடியாக எடுக்கலாம். மேலே உள்ள _swap போல, செயற்கூறு உள்நாட்டில் அழைக்கப்பட்டால், அளவுருக்கள் memory-இல் சேமிக்கப்பட வேண்டும். அழைக்கப்பட்ட ஒப்பந்தத்தின் கண்ணோட்டத்தில் calldata என்பது படிக்க மட்டுமேயானது.
uint அல்லது address போன்ற அளவிடக்கூடிய வகைகளுடன் தொகுப்பான் நமக்கான சேமிப்பகத் தேர்வைக் கையாளுகிறது, ஆனால் நீளமான மற்றும் அதிக விலையுயர்ந்த வரிசைகளுடன், பயன்படுத்த வேண்டிய சேமிப்பகத்தின் வகையை நாங்கள் குறிப்பிடுகிறோம்.
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
திரும்பப் பெறும் மதிப்புகள் எப்போதும் நினைவகத்தில் வழங்கப்படும்.
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
ஒவ்வொரு பரிமாற்றத்திலும் வாங்க வேண்டிய தொகையைக் கணக்கிடுங்கள். முடிவு வர்த்தகர் ஏற்றுக்கொள்ளத் தயாராக இருக்கும் குறைந்தபட்சத்தை விடக் குறைவாக இருந்தால், பரிவர்த்தனையிலிருந்து மீளமைக்கவும்.
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
இறுதியாக, முதல் ஜோடி பரிமாற்றத்திற்கான கணக்கிற்கு ஆரம்ப ERC-20 வில்லையைப் பரிமாற்றம் செய்து _swap-ஐ அழைக்கவும். இவை அனைத்தும் ஒரே பரிவர்த்தனையில் நடக்கின்றன, எனவே எதிர்பாராத வில்லைகள் ஏதேனும் இருந்தால் அவை இந்தப் பரிமாற்றத்தின் ஒரு பகுதி என்பதை ஜோடி பரிமாற்றம் அறியும்.
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
முந்தைய செயற்கூறான swapTokensForTokens, ஒரு வர்த்தகர் தான் கொடுக்கத் தயாராக இருக்கும் உள்ளீட்டு வில்லைகளின் சரியான எண்ணிக்கையையும், பதிலுக்கு அவர் பெறத் தயாராக இருக்கும் வெளியீட்டு வில்லைகளின் குறைந்தபட்ச எண்ணிக்கையையும் குறிப்பிட அனுமதிக்கிறது. இந்தச் செயற்கூறு தலைகீழ் பரிமாற்றத்தைச் செய்கிறது, இது ஒரு வர்த்தகர் தான் விரும்பும் வெளியீட்டு வில்லைகளின் எண்ணிக்கையையும், அவற்றுக்காக அவர் செலுத்தத் தயாராக இருக்கும் உள்ளீட்டு வில்லைகளின் அதிகபட்ச எண்ணிக்கையையும் குறிப்பிட அனுமதிக்கிறது.
இரண்டு சந்தர்ப்பங்களிலும், வர்த்தகர் முதலில் இந்த விளிம்பு ஒப்பந்தத்திற்கு அவற்றைப் பரிமாற்றம் செய்ய அனுமதித்தொகையை வழங்க வேண்டும்.
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
// ஏதேனும் இருந்தால், டஸ்ட் ஈதரைத் திரும்பப் பெறவும்
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}
இந்த நான்கு வகைகளும் ETH மற்றும் வில்லைகளுக்கு இடையிலான வர்த்தகத்தை உள்ளடக்கியவை. ஒரே வித்தியாசம் என்னவென்றால், நாங்கள் வர்த்தகரிடமிருந்து ETH-ஐப் பெற்று WETH-ஐ அச்சிடப் பயன்படுத்துகிறோம், அல்லது பாதையில் உள்ள கடைசிப் பரிமாற்றத்திலிருந்து WETH-ஐப் பெற்று அதை எரித்து, வர்த்தகருக்கு அதன் விளைவாக வரும் ETH-ஐத் திருப்பி அனுப்புகிறோம்.
// **** பரிமாற்றம் (பரிமாற்றக் கட்டண வில்லைகளை ஆதரிக்கிறது) ****
// ஆரம்பத் தொகை ஏற்கனவே முதல் ஜோடிக்கு அனுப்பப்பட்டிருக்க வேண்டும்
function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
(இந்தச் சிக்கலைத் (opens in a new tab)) தீர்க்க, பரிமாற்றம் அல்லது சேமிப்புக் கட்டணங்களைக் கொண்ட வில்லைகளைப் பரிமாற்றம் செய்வதற்கான உள் செயற்கூறு இதுவாகும்.
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
uint amountInput;
uint amountOutput;
{ // அடுக்கு மிகவும் ஆழமான பிழைகளைத் தவிர்ப்பதற்கான நோக்கம்
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
பரிமாற்றக் கட்டணங்கள் காரணமாக, ஒவ்வொரு பரிமாற்றத்திலிருந்தும் நாம் எவ்வளவு பெறுகிறோம் என்பதைச் சொல்ல getAmountsOut செயற்கூறை நாம் நம்ப முடியாது (அசல் _swap-ஐ அழைப்பதற்கு முன்பு நாம் செய்யும் விதம்). அதற்குப் பதிலாக நாம் முதலில் பரிமாற்றம் செய்ய வேண்டும், பின்னர் எத்தனை வில்லைகளைத் திரும்பப் பெற்றோம் என்பதைப் பார்க்க வேண்டும்.
குறிப்பு: கோட்பாட்டில் நாம் _swap-க்கு பதிலாக இந்தச் செயற்கூறைப் பயன்படுத்தலாம், ஆனால் சில சந்தர்ப்பங்களில் (எடுத்துக்காட்டாக, தேவையான குறைந்தபட்சத்தைப் பூர்த்தி செய்ய முடிவில் போதுமான அளவு இல்லாததால் பரிமாற்றம் மீளமைக்கப்பட்டால்) அது அதிக எரிவாயு செலவாகும். பரிமாற்றக் கட்டண வில்லைகள் மிகவும் அரிதானவை, எனவே நாம் அவற்றுக்கு இடமளிக்க வேண்டியிருக்கும் அதே வேளையில், அனைத்துப் பரிமாற்றங்களும் அவற்றில் ஒன்றின் வழியாகவாவது செல்லும் என்று கருத வேண்டிய அவசியமில்லை.
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
}
}
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
payable
ensure(deadline)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
uint amountIn = msg.value;
IWETH(WETH).deposit{value: amountIn}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}
இவை சாதாரண வில்லைகளுக்குப் பயன்படுத்தப்படும் அதே வகைகளாகும், ஆனால் அவை அதற்குப் பதிலாக _swapSupportingFeeOnTransferTokens-ஐ அழைக்கின்றன.
// **** நிரலகச் செயல்பாடுகள் ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
இந்தச் செயற்கூறுகள் UniswapV2Library செயற்கூறுகளை அழைக்கும் பினாமிகள் மட்டுமே.
UniswapV2Migrator.sol
பழைய v1-இலிருந்து v2-க்கு பரிமாற்றங்களை நகர்த்த இந்த ஒப்பந்தம் பயன்படுத்தப்பட்டது. இப்போது அவை நகர்த்தப்பட்டுவிட்டதால், இது இனி பொருந்தாது.
நிரலகங்கள்
SafeMath நிரலகம் (opens in a new tab) நன்கு ஆவணப்படுத்தப்பட்டுள்ளது, எனவே அதை இங்கே ஆவணப்படுத்த வேண்டிய அவசியமில்லை.
Math
இந்த நிரலகத்தில் Solidity குறியீட்டில் பொதுவாகத் தேவைப்படாத சில கணிதச் செயற்கூறுகள் உள்ளன, எனவே அவை மொழியின் ஒரு பகுதியாக இல்லை.
pragma solidity =0.5.16;
// பல்வேறு கணித செயல்பாடுகளைச் செய்வதற்கான ஒரு நிரலகம்
library Math {
function min(uint x, uint y) internal pure returns (uint z) {
z = x < y ? x : y;
}
// பாபிலோனிய முறை (https://wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
வர்க்கமூலத்தை விட அதிகமான மதிப்பீடாக x உடன் தொடங்கவும் (இதன் காரணமாகவே 1-3 ஐ சிறப்பு நிகழ்வுகளாகக் கருத வேண்டும்).
while (x < z) {
z = x;
x = (y / x + x) / 2;
முந்தைய மதிப்பீடு மற்றும் நாம் வர்க்கமூலத்தைக் கண்டுபிடிக்க முயற்சிக்கும் எண்ணை முந்தைய மதிப்பீட்டால் வகுக்கக் கிடைக்கும் சராசரியைக் கொண்டு, இன்னும் நெருக்கமான மதிப்பீட்டைப் பெறவும். புதிய மதிப்பீடு ஏற்கனவே உள்ளதை விடக் குறையாத வரை இதைத் திரும்பச் செய்யவும். மேலும் விவரங்களுக்கு, இங்கே பார்க்கவும் (opens in a new tab).
}
} else if (y != 0) {
z = 1;
பூஜ்ஜியத்தின் வர்க்கமூலம் நமக்கு ஒருபோதும் தேவைப்படாது. ஒன்று, இரண்டு மற்றும் மூன்றின் வர்க்கமூலங்கள் தோராயமாக ஒன்றுதான் (நாம் முழு எண்களைப் பயன்படுத்துகிறோம், எனவே பின்னத்தைப் புறக்கணிக்கிறோம்).
}
}
}
நிலையான புள்ளி பின்னங்கள் (UQ112x112)
இந்த நிரலகம் பின்னங்களைக் கையாளுகிறது, இவை பொதுவாக எத்திரியம் (Ethereum) எண்கணிதத்தின் ஒரு பகுதியாக இருக்காது. இது x என்ற எண்ணை x*2^112 என குறியாக்கம் செய்வதன் மூலம் இதைச் செய்கிறது. இது அசல் கூட்டல் மற்றும் கழித்தல் செயல்பாட்டுக் குறியீடுகளை (opcodes) எந்த மாற்றமும் இல்லாமல் பயன்படுத்த அனுமதிக்கிறது.
pragma solidity =0.5.16;
// பைனரி நிலையான புள்ளி எண்களைக் கையாளுவதற்கான ஒரு நிரலகம் (https://wikipedia.org/wiki/Q_(number_format))
// வரம்பு: [0, 2**112 - 1]
// தெளிவுத்திறன்: 1 / 2**112
library UQ112x112 {
uint224 constant Q112 = 2**112;
Q112 என்பது ஒன்றிற்கான குறியாக்கமாகும்.
// uint112 ஐ UQ112x112 ஆக குறியாக்கம் செய்யவும்
function encode(uint112 y) internal pure returns (uint224 z) {
z = uint224(y) * Q112; // ஒருபோதும் வழியாது
}
y என்பது uint112 ஆக இருப்பதால், அதன் அதிகபட்ச மதிப்பு 2^112-1 ஆக இருக்கலாம். அந்த எண்ணை இன்னும் UQ112x112 ஆக குறியாக்கம் செய்ய முடியும்.
// UQ112x112 ஐ uint112 ஆல் வகுத்து, UQ112x112 ஐ வழங்குகிறது
function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
z = x / uint224(y);
}
}
நாம் இரண்டு UQ112x112 மதிப்புகளை வகுத்தால், அதன் முடிவு 2^112 ஆல் பெருக்கப்படாது. எனவே அதற்குப் பதிலாகப் பகுதிக்காக (denominator) ஒரு முழு எண்ணை எடுத்துக்கொள்கிறோம். பெருக்கலைச் செய்வதற்கும் இதேபோன்ற ஒரு தந்திரத்தைப் பயன்படுத்த வேண்டியிருந்திருக்கும், ஆனால் நாம் UQ112x112 மதிப்புகளின் பெருக்கலைச் செய்ய வேண்டியதில்லை.
UniswapV2Library
இந்த நிரலகம் புற ஒப்பந்தங்களால் (periphery contracts) மட்டுமே பயன்படுத்தப்படுகிறது
pragma solidity >=0.5.0;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import "./SafeMath.sol";
library UniswapV2Library {
using SafeMath for uint;
// வரிசைப்படுத்தப்பட்ட வில்லை முகவரிகளை வழங்குகிறது, இந்த வரிசையில் வரிசைப்படுத்தப்பட்ட ஜோடிகளிலிருந்து திரும்பும் மதிப்புகளைக் கையாளப் பயன்படுகிறது
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
இரண்டு வில்லைகளையும் (tokens) முகவரி வாரியாக வரிசைப்படுத்தவும், இதனால் அவற்றுக்கான ஜோடிப் பரிமாற்றத்தின் (pair exchange) முகவரியை நாம் பெற முடியும். இது அவசியமானது, ஏனென்றால் இல்லையெனில் நமக்கு இரண்டு சாத்தியக்கூறுகள் இருக்கும், ஒன்று A,B அளவுருக்களுக்கும் மற்றொன்று B,A அளவுருக்களுக்கும், இது ஒன்றுக்குப் பதிலாக இரண்டு பரிமாற்றங்களுக்கு வழிவகுக்கும்.
// எந்தவொரு வெளிப்புற அழைப்புகளையும் செய்யாமல் ஒரு ஜோடிக்கான CREATE2 முகவரியைக் கணக்கிடுகிறது
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init குறியீடு ஹாஷ்
))));
}
இந்தச் செயற்கூறு இரண்டு வில்லைகளுக்கான ஜோடிப் பரிமாற்றத்தின் முகவரியைக் கணக்கிடுகிறது. இந்த ஒப்பந்தம் CREATE2 செயல்பாட்டுக் குறியீட்டைப் (opcode) (opens in a new tab) பயன்படுத்தி உருவாக்கப்பட்டுள்ளது, எனவே அது பயன்படுத்தும் அளவுருக்கள் நமக்குத் தெரிந்தால் அதே வழிமுறையைப் பயன்படுத்தி முகவரியைக் கணக்கிடலாம். இது தொழிற்சாலையிடம் (factory) கேட்பதை விட மிகவும் மலிவானது, மேலும்
// ஒரு ஜோடிக்கான இருப்புக்களைப் பெற்று வரிசைப்படுத்துகிறது
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
இந்தச் செயற்கூறு ஜோடிப் பரிமாற்றம் கொண்டுள்ள இரண்டு வில்லைகளின் இருப்புகளை (reserves) வழங்குகிறது. இது வில்லைகளை எந்த வரிசையிலும் பெறலாம் என்பதையும், உள் பயன்பாட்டிற்காக அவற்றை வரிசைப்படுத்துகிறது என்பதையும் கவனத்தில் கொள்ளவும்.
// ஒரு சொத்தின் குறிப்பிட்ட அளவு மற்றும் ஜோடி இருப்புக்கள் கொடுக்கப்பட்டால், மற்ற சொத்தின் சமமான அளவை வழங்குகிறது
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
எந்தக் கட்டணமும் இல்லை என்றால், வில்லை A-க்கு ஈடாக நீங்கள் பெறும் வில்லை B-இன் அளவை இந்தச் செயற்கூறு உங்களுக்கு வழங்குகிறது. இந்தப் பரிமாற்றம் (transfer) மாற்று விகிதத்தை மாற்றுகிறது என்பதை இந்தக் கணக்கீடு கருத்தில் கொள்கிறது.
// ஒரு சொத்தின் உள்ளீட்டு அளவு மற்றும் ஜோடி இருப்புக்கள் கொடுக்கப்பட்டால், மற்ற சொத்தின் அதிகபட்ச வெளியீட்டு அளவை வழங்குகிறது
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
ஜோடிப் பரிமாற்றத்தைப் பயன்படுத்த எந்தக் கட்டணமும் இல்லை என்றால், மேலே உள்ள quote செயற்கூறு சிறப்பாகச் செயல்படும். இருப்பினும், 0.3% பரிமாற்றக் கட்டணம் இருந்தால், நீங்கள் உண்மையில் பெறும் தொகை குறைவாக இருக்கும். இந்தச் செயற்கூறு பரிமாற்றக் கட்டணத்திற்குப் பிந்தைய தொகையைக் கணக்கிடுகிறது.
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
Solidity இயல்பாகவே பின்னங்களைக் கையாளுவதில்லை, எனவே நாம் தொகையை 0.997 ஆல் பெருக்க முடியாது. அதற்குப் பதிலாக, நாம் தொகுதியை (numerator) 997 ஆலும், பகுதியை (denominator) 1000 ஆலும் பெருக்குகிறோம், இதன் மூலம் அதே முடிவை அடைகிறோம்.
// ஒரு சொத்தின் வெளியீட்டு அளவு மற்றும் ஜோடி இருப்புக்கள் கொடுக்கப்பட்டால், மற்ற சொத்தின் தேவையான உள்ளீட்டு அளவை வழங்குகிறது
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
இந்தச் செயற்கூறு தோராயமாக அதே காரியத்தையே செய்கிறது, ஆனால் இது வெளியீட்டுத் தொகையைப் பெற்று உள்ளீட்டை வழங்குகிறது.
// எந்தவொரு ஜோடிகளின் எண்ணிக்கையிலும் சங்கிலியால் பிணைக்கப்பட்ட getAmountOut கணக்கீடுகளைச் செய்கிறது
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
// எந்தவொரு ஜோடிகளின் எண்ணிக்கையிலும் சங்கிலியால் பிணைக்கப்பட்ட getAmountIn கணக்கீடுகளைச் செய்கிறது
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}
பல ஜோடிப் பரிமாற்றங்கள் வழியாகச் செல்ல வேண்டியிருக்கும் போது மதிப்புகளை அடையாளம் காண்பதை இந்த இரண்டு செயற்கூறுகளும் கையாளுகின்றன.
Transfer Helper
இந்த நிரலகம் (opens in a new tab) ERC-20 மற்றும் எத்திரியம் பரிமாற்றங்களைச் சுற்றி வெற்றிக்கான சரிபார்ப்புகளைச் சேர்க்கிறது, இதன் மூலம் ஒரு மீளமை (revert) மற்றும் false மதிப்புத் திருப்பம் ஆகியவற்றை ஒரே மாதிரியாகக் கையாளுகிறது.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.0;
// ERC-20 வில்லைகளுடன் தொடர்புகொள்வதற்கும், தொடர்ந்து true/false ஐ வழங்காத ETH ஐ அனுப்புவதற்குமான உதவி முறைகள்
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
நாம் வேறு ஒரு ஒப்பந்தத்தை இரண்டு வழிகளில் ஒன்றில் அழைக்கலாம்:
- ஒரு செயற்கூறு அழைப்பை உருவாக்க இடைமுக வரையறையைப் (interface definition) பயன்படுத்துதல்
- அழைப்பை உருவாக்க பயன்பாட்டு பைனரி இடைமுகத்தை (application binary interface - ABI) (opens in a new tab) "கைமுறையாகப்" பயன்படுத்துதல். குறியீட்டின் ஆசிரியர் இதைத்தான் செய்ய முடிவு செய்துள்ளார்.
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeApprove: approve failed'
);
}
ERC-20 தரநிலைக்கு முன்பு உருவாக்கப்பட்ட வில்லைகளுடனான பின்னோக்கிய இணக்கத்தன்மைக்காக (backwards compatibility), ஒரு ERC-20 அழைப்பு மீளமைக்கப்படுவதன் மூலம் (reverting) தோல்வியடையலாம் (இந்த நிலையில் success என்பது false ஆக இருக்கும்) அல்லது வெற்றிகரமாக முடிந்து false மதிப்பை வழங்குவதன் மூலம் தோல்வியடையலாம் (இந்த நிலையில் வெளியீட்டுத் தரவு இருக்கும், அதை நீங்கள் பூலியனாக (boolean) குறிவிலக்கினால் (decode) false கிடைக்கும்).
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeTransfer: transfer failed'
);
}
இந்தச் செயற்கூறு ERC-20-இன் பரிமாற்றச் செயல்பாட்டை (transfer functionality) (opens in a new tab) செயல்படுத்துகிறது, இது ஒரு கணக்கு வேறு ஒரு கணக்கால் வழங்கப்பட்ட அனுமதித்தொகையைச் (allowance) செலவிட அனுமதிக்கிறது.
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::transferFrom: transferFrom failed'
);
}
இந்தச் செயற்கூறு ERC-20-இன் transferFrom செயல்பாட்டை (opens in a new tab) செயல்படுத்துகிறது, இது ஒரு கணக்கு வேறு ஒரு கணக்கால் வழங்கப்பட்ட அனுமதித்தொகையைச் செலவிட அனுமதிக்கிறது.
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
}
இந்தச் செயற்கூறு ஈதரை (ether) ஒரு கணக்கிற்குப் பரிமாற்றம் செய்கிறது. வேறு ஒரு ஒப்பந்தத்திற்கான எந்தவொரு அழைப்பும் ஈதரை அனுப்ப முயற்சிக்கலாம். நாம் உண்மையில் எந்தச் செயற்கூறையும் அழைக்கத் தேவையில்லை என்பதால், அழைப்புடன் எந்தத் தரவையும் நாம் அனுப்புவதில்லை.
முடிவுரை
இது சுமார் 50 பக்கங்களைக் கொண்ட ஒரு நீண்ட கட்டுரையாகும். நீங்கள் இங்கு வரை வந்துவிட்டீர்கள் என்றால், வாழ்த்துகள்! (சிறிய மாதிரி நிரல்களுக்கு மாறாக) ஒரு நிஜ-வாழ்க்கை பயன்பாட்டை எழுதுவதில் உள்ள பரிசீலனைகளை இதுவரை நீங்கள் புரிந்துகொண்டிருப்பீர்கள் என்றும், உங்கள் சொந்த பயன்பாட்டுத் தேவைகளுக்கான ஒப்பந்தங்களை சிறப்பாக எழுத முடியும் என்றும் நம்புகிறோம்.
இப்போது சென்று பயனுள்ள ஒன்றை எழுதி எங்களை ஆச்சரியப்படுத்துங்கள்.
