முக்கிய உள்ளடக்கத்திற்குச் செல்லவும்

Uniswap-v2 ஒப்பந்தத்தின் வழிகாட்டி

Solidity
dapps
இடைநிலையாளர்
ஓரி பொமரன்ட்ஸ்
1 மே, 2021
51 நிமிட வாசிப்பு

அறிமுகம்

Uniswap v2 (opens in a new tab) எந்த இரண்டு ERC-20 டோக்கன்களுக்கும் இடையே ஒரு பரிமாற்ற சந்தையை உருவாக்க முடியும். இந்தக் கட்டுரையில், இந்த நெறிமுறையைச் செயல்படுத்தும் ஒப்பந்தங்களுக்கான மூலக் குறியீட்டை (source code) மதிப்பாய்வு செய்து, அவை ஏன் அவ்வாறு எழுதப்பட்டுள்ளன என்பதைப் பார்ப்போம்.

Uniswap என்ன செய்கிறது?

அடிப்படையில், இரண்டு வகையான பயனர்கள் உள்ளனர்: பணப்புழக்கத்தை வழங்குபவர்கள் (liquidity providers) மற்றும் வர்த்தகர்கள் (traders).

பணப்புழக்கத்தை வழங்குபவர்கள் பரிமாறிக்கொள்ளக்கூடிய இரண்டு டோக்கன்களைக் குளத்திற்கு (pool) வழங்குகிறார்கள் (அவற்றை நாம் Token0 மற்றும் Token1 என்று அழைப்போம்). அதற்குப் பதிலாக, அவர்கள் குளத்தின் பகுதி உரிமையைக் குறிக்கும் மூன்றாவது டோக்கனைப் பெறுகிறார்கள், இது பணப்புழக்க டோக்கன் (liquidity token) என்று அழைக்கப்படுகிறது.

வர்த்தகர்கள் ஒரு வகையான டோக்கனைக் குளத்திற்கு அனுப்பி, பணப்புழக்கத்தை வழங்குபவர்கள் வழங்கிய குளத்திலிருந்து மற்றொன்றைப் பெறுகிறார்கள் (எடுத்துக்காட்டாக, Token0-ஐ அனுப்பி Token1-ஐப் பெறுவார்கள்). பரிமாற்ற விகிதமானது குளத்தில் உள்ள Token0 மற்றும் Token1-களின் ஒப்பீட்டு எண்ணிக்கையால் தீர்மானிக்கப்படுகிறது. கூடுதலாக, பணப்புழக்கக் குளத்திற்கான வெகுமதியாகக் குளம் ஒரு சிறிய சதவீதத்தை எடுத்துக்கொள்கிறது.

பணப்புழக்கத்தை வழங்குபவர்கள் தங்கள் சொத்துக்களைத் திரும்பப் பெற விரும்பினால், அவர்கள் பூல் டோக்கன்களை எரித்துவிட்டு (burn), வெகுமதிகளில் தங்கள் பங்கு உட்படத் தங்கள் டோக்கன்களைத் திரும்பப் பெறலாம்.

முழுமையான விளக்கத்திற்கு இங்கே கிளிக் செய்யவும் (opens in a new tab).

ஏன் v2? ஏன் v3 இல்லை?

Uniswap v3 (opens in a new tab) என்பது v2-ஐ விட மிகவும் சிக்கலான ஒரு மேம்படுத்தலாகும். முதலில் v2-ஐக் கற்றுக்கொண்டு பின்னர் v3-க்குச் செல்வது எளிது.

மைய ஒப்பந்தங்கள் மற்றும் விளிம்பு ஒப்பந்தங்கள் (Core Contracts vs Periphery Contracts)

Uniswap v2 இரண்டு கூறுகளாகப் பிரிக்கப்பட்டுள்ளது, ஒரு மையம் (core) மற்றும் ஒரு விளிம்பு (periphery). இந்தப் பிரிப்பானது, சொத்துக்களை வைத்திருக்கும் மற்றும் பாதுகாப்பாக இருக்க வேண்டிய மைய ஒப்பந்தங்களை எளிமையாகவும் தணிக்கை செய்ய எளிதாகவும் இருக்க அனுமதிக்கிறது. வர்த்தகர்களுக்குத் தேவையான அனைத்து கூடுதல் செயல்பாடுகளையும் விளிம்பு ஒப்பந்தங்கள் மூலம் வழங்க முடியும்.

தரவு மற்றும் கட்டுப்பாட்டு ஓட்டங்கள்

Uniswap இன் மூன்று முக்கிய செயல்களை நீங்கள் செய்யும்போது நிகழும் தரவு மற்றும் கட்டுப்பாட்டு ஓட்டம் இதுதான்:

  1. வெவ்வேறு டோக்கன்களுக்கு இடையே மாற்றுதல் (Swap)
  2. சந்தைக்கு பணப்புழக்கத்தைச் (liquidity) சேர்த்து, ஜோடி பரிமாற்ற ERC-20 பணப்புழக்க டோக்கன்களுடன் வெகுமதி பெறுதல்
  3. ERC-20 பணப்புழக்க டோக்கன்களை எரித்து (Burn), வர்த்தகர்கள் பரிமாற்றம் செய்ய ஜோடி பரிமாற்றம் அனுமதிக்கும் ERC-20 டோக்கன்களைத் திரும்பப் பெறுதல்

மாற்றுதல் (Swap)

இது வர்த்தகர்களால் பயன்படுத்தப்படும் மிகவும் பொதுவான ஓட்டமாகும்:

அழைப்பாளர் (Caller)

  1. மாற்றப்பட வேண்டிய தொகையில் periphery கணக்கிற்கு ஒரு அனுமதியை (allowance) வழங்கவும்.
  2. periphery ஒப்பந்தத்தின் பல swap செயல்பாடுகளில் ஒன்றை அழைக்கவும் (எதை அழைப்பது என்பது ETH உள்ளதா இல்லையா, வர்த்தகர் டெபாசிட் செய்ய வேண்டிய டோக்கன்களின் அளவைக் குறிப்பிடுகிறாரா அல்லது திரும்பப் பெற வேண்டிய டோக்கன்களின் அளவைக் குறிப்பிடுகிறாரா என்பதைப் பொறுத்தது). ஒவ்வொரு swap செயல்பாடும் ஒரு path-ஐ ஏற்றுக்கொள்கிறது, இது கடந்து செல்ல வேண்டிய பரிமாற்றங்களின் வரிசையாகும் (array).

periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)

  1. பாதையில் உள்ள ஒவ்வொரு பரிமாற்றத்திலும் வர்த்தகம் செய்ய வேண்டிய தொகைகளை அடையாளம் காணவும்.
  2. பாதையின் மீது மீண்டும் மீண்டும் செயல்படுகிறது (Iterates). வழியில் உள்ள ஒவ்வொரு பரிமாற்றத்திற்கும் இது உள்ளீட்டு டோக்கனை அனுப்புகிறது, பின்னர் பரிமாற்றத்தின் swap செயல்பாட்டை அழைக்கிறது. பெரும்பாலான சந்தர்ப்பங்களில் டோக்கன்களுக்கான இலக்கு முகவரி பாதையில் உள்ள அடுத்த ஜோடி பரிமாற்றமாகும். இறுதிப் பரிமாற்றத்தில் இது வர்த்தகர் வழங்கிய முகவரியாகும்.

மைய ஒப்பந்தத்தில் (UniswapV2Pair.sol)

  1. மைய ஒப்பந்தம் ஏமாற்றப்படவில்லை என்பதையும், மாற்றத்திற்குப் பிறகு போதுமான பணப்புழக்கத்தை பராமரிக்க முடியும் என்பதையும் சரிபார்க்கவும்.
  2. அறியப்பட்ட இருப்புக்களுக்கு (reserves) கூடுதலாக நம்மிடம் எத்தனை கூடுதல் டோக்கன்கள் உள்ளன என்பதைப் பார்க்கவும். அந்தத் தொகைதான் பரிமாற்றம் செய்ய நாம் பெற்ற உள்ளீட்டு டோக்கன்களின் எண்ணிக்கையாகும்.
  3. வெளியீட்டு டோக்கன்களை இலக்குக்கு அனுப்பவும்.
  4. இருப்புத் தொகைகளைப் புதுப்பிக்க _update-ஐ அழைக்கவும்

மீண்டும் periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)

  1. தேவையான எந்தவொரு சுத்திகரிப்புப் பணியையும் செய்யவும் (எடுத்துக்காட்டாக, வர்த்தகருக்கு அனுப்ப ETH-ஐத் திரும்பப் பெற WETH டோக்கன்களை எரிக்கவும்)

பணப்புழக்கத்தைச் சேர்த்தல் (Add Liquidity)

அழைப்பாளர் (Caller)

  1. பணப்புழக்கக் குளத்தில் (liquidity pool) சேர்க்கப்பட வேண்டிய தொகைகளில் periphery கணக்கிற்கு ஒரு அனுமதியை வழங்கவும்.
  2. periphery ஒப்பந்தத்தின் addLiquidity செயல்பாடுகளில் ஒன்றை அழைக்கவும்.

periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)

  1. தேவைப்பட்டால் புதிய ஜோடி பரிமாற்றத்தை உருவாக்கவும்
  2. ஏற்கனவே ஒரு ஜோடி பரிமாற்றம் இருந்தால், சேர்க்க வேண்டிய டோக்கன்களின் அளவைக் கணக்கிடவும். இது இரண்டு டோக்கன்களுக்கும் ஒரே மாதிரியான மதிப்பாக இருக்க வேண்டும், எனவே புதிய டோக்கன்களுக்கும் இருக்கும் டோக்கன்களுக்கும் அதே விகிதம் இருக்க வேண்டும்.
  3. தொகைகள் ஏற்றுக்கொள்ளத்தக்கவையா என்பதைச் சரிபார்க்கவும் (அழைப்பாளர்கள் ஒரு குறைந்தபட்ச தொகையைக் குறிப்பிடலாம், அதற்குக் கீழே அவர்கள் பணப்புழக்கத்தைச் சேர்க்க விரும்ப மாட்டார்கள்)
  4. மைய ஒப்பந்தத்தை அழைக்கவும்.

மைய ஒப்பந்தத்தில் (UniswapV2Pair.sol)

  1. பணப்புழக்க டோக்கன்களை உருவாக்கி (Mint) அவற்றை அழைப்பாளருக்கு அனுப்பவும்
  2. இருப்புத் தொகைகளைப் புதுப்பிக்க _update-ஐ அழைக்கவும்

பணப்புழக்கத்தை அகற்றுதல் (Remove Liquidity)

அழைப்பாளர் (Caller)

  1. அடிப்படை டோக்கன்களுக்கு ஈடாக எரிக்கப்பட வேண்டிய பணப்புழக்க டோக்கன்களின் அனுமதியை periphery கணக்கிற்கு வழங்கவும்.
  2. periphery ஒப்பந்தத்தின் removeLiquidity செயல்பாடுகளில் ஒன்றை அழைக்கவும்.

periphery ஒப்பந்தத்தில் (UniswapV2Router02.sol)

  1. பணப்புழக்க டோக்கன்களை ஜோடி பரிமாற்றத்திற்கு அனுப்பவும்

மைய ஒப்பந்தத்தில் (UniswapV2Pair.sol)

  1. எரிக்கப்பட்ட டோக்கன்களுக்கு விகிதாசாரமாக அடிப்படை டோக்கன்களை இலக்கு முகவரிக்கு அனுப்பவும். எடுத்துக்காட்டாக, குளத்தில் 1000 A டோக்கன்கள், 500 B டோக்கன்கள் மற்றும் 90 பணப்புழக்க டோக்கன்கள் இருந்தால், எரிக்க 9 டோக்கன்களைப் பெற்றால், நாங்கள் 10% பணப்புழக்க டோக்கன்களை எரிக்கிறோம், எனவே பயனருக்கு 100 A டோக்கன்களையும் 50 B டோக்கன்களையும் திருப்பி அனுப்புகிறோம்.
  2. பணப்புழக்க டோக்கன்களை எரிக்கவும்
  3. இருப்புத் தொகைகளைப் புதுப்பிக்க _update-ஐ அழைக்கவும்

முக்கிய ஒப்பந்தங்கள்

இவை பணப்புழக்கத்தை (liquidity) வைத்திருக்கும் பாதுகாப்பான ஒப்பந்தங்கள் ஆகும்.

UniswapV2Pair.sol

இந்த ஒப்பந்தம் (opens in a new tab) டோக்கன்களைப் பரிமாற்றும் உண்மையான பூலை (pool) செயல்படுத்துகிறது. இதுவே Uniswap-இன் முக்கிய செயல்பாடாகும்.

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';

ஒப்பந்தம் தெரிந்துகொள்ள வேண்டிய அனைத்து இடைமுகங்களும் (interfaces) இவைதான், ஏனெனில் ஒப்பந்தம் அவற்றைச் செயல்படுத்துகிறது (IUniswapV2Pair மற்றும் UniswapV2ERC20) அல்லது அவற்றைச் செயல்படுத்தும் ஒப்பந்தங்களை இது அழைக்கிறது.

1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {

இந்த ஒப்பந்தம் UniswapV2ERC20-லிருந்து மரபுரிமையாகப் பெறுகிறது (inherits), இது பணப்புழக்க டோக்கன்களுக்கான (liquidity tokens) ERC-20 செயல்பாடுகளை வழங்குகிறது.

1 using SafeMath for uint;

ஓவர்ஃப்ளோக்கள் (overflows) மற்றும் அண்டர்ஃப்ளோக்களைத் (underflows) தவிர்க்க SafeMath லைப்ரரி (opens in a new tab) பயன்படுத்தப்படுகிறது. இது முக்கியமானது, இல்லையெனில் ஒரு மதிப்பு -1 ஆக இருக்க வேண்டிய இடத்தில், அதற்குப் பதிலாக 2^256-1 ஆக மாறும் சூழ்நிலை ஏற்படலாம்.

1 using UQ112x112 for uint224;

பூல் ஒப்பந்தத்தில் உள்ள பல கணக்கீடுகளுக்குப் பின்னங்கள் (fractions) தேவைப்படுகின்றன. இருப்பினும், EVM-ஆல் பின்னங்கள் ஆதரிக்கப்படுவதில்லை. இதற்கு Uniswap கண்டறிந்த தீர்வு 224 பிட் மதிப்புகளைப் பயன்படுத்துவதாகும், இதில் முழு எண் பகுதிக்கு 112 பிட்களும், பின்னத்திற்கு 112 பிட்களும் பயன்படுத்தப்படுகின்றன. எனவே 1.0 என்பது 2^112 ஆகவும், 1.5 என்பது 2^112 + 2^111 ஆகவும் குறிக்கப்படுகிறது.

இந்த லைப்ரரி பற்றிய கூடுதல் விவரங்கள் ஆவணத்தில் பின்னர் கிடைக்கின்றன.

மாறிகள்

1 uint public constant MINIMUM_LIQUIDITY = 10**3;

பூஜ்ஜியத்தால் வகுக்கப்படும் நிகழ்வுகளைத் தவிர்க்க, எப்போதும் இருக்கும் குறைந்தபட்ச பணப்புழக்க டோக்கன்கள் உள்ளன (ஆனால் அவை கணக்கு பூஜ்ஜியத்திற்குச் சொந்தமானவை). அந்த எண் MINIMUM_LIQUIDITY, அதாவது ஆயிரம்.

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

இது ERC-20 பரிமாற்றச் செயல்பாட்டிற்கான ABI செலக்டர் ஆகும். இரண்டு டோக்கன் கணக்குகளில் ERC-20 டோக்கன்களைப் பரிமாற்ற இது பயன்படுத்தப்படுகிறது.

1 address public factory;

இந்த பூலை உருவாக்கிய ஃபேக்டரி (factory) ஒப்பந்தம் இதுவாகும். ஒவ்வொரு பூலும் இரண்டு ERC-20 டோக்கன்களுக்கு இடையிலான பரிமாற்றமாகும், ஃபேக்டரி என்பது இந்த பூல்கள் அனைத்தையும் இணைக்கும் ஒரு மையப் புள்ளியாகும்.

1 address public token0;
2 address public token1;

இந்த பூல் மூலம் பரிமாறிக்கொள்ளக்கூடிய இரண்டு வகையான ERC-20 டோக்கன்களுக்கான ஒப்பந்தங்களின் முகவரிகள் உள்ளன.

1 uint112 private reserve0; // ஒற்றை சேமிப்பக ஸ்லாட்டைப் பயன்படுத்துகிறது, getReserves மூலம் அணுகலாம்
2 uint112 private reserve1; // ஒற்றை சேமிப்பக ஸ்லாட்டைப் பயன்படுத்துகிறது, getReserves மூலம் அணுகலாம்

ஒவ்வொரு டோக்கன் வகைக்கும் பூல் வைத்திருக்கும் இருப்புகள் (reserves). இரண்டும் ஒரே அளவிலான மதிப்பைக் குறிக்கின்றன என்று நாங்கள் கருதுகிறோம், எனவே ஒவ்வொரு token0-இன் மதிப்பும் reserve1/reserve0 token1-களுக்குச் சமம்.

1 uint32 private blockTimestampLast; // ஒற்றை சேமிப்பக ஸ்லாட்டைப் பயன்படுத்துகிறது, getReserves மூலம் அணுகலாம்

பரிமாற்றம் நடந்த கடைசி பிளாக்கின் நேரமுத்திரை (timestamp), காலப்போக்கில் பரிமாற்ற விகிதங்களைக் கண்காணிக்கப் பயன்படுத்தப்படுகிறது.

Ethereum ஒப்பந்தங்களின் மிகப்பெரிய கேஸ் (gas) செலவுகளில் ஒன்று சேமிப்பகமாகும் (storage), இது ஒப்பந்தத்தின் ஒரு அழைப்பிலிருந்து அடுத்த அழைப்பு வரை நிலைத்திருக்கும். ஒவ்வொரு சேமிப்பக செல்லும் 256 பிட்கள் நீளமானது. எனவே reserve0, reserve1 மற்றும் blockTimestampLast ஆகிய மூன்று மாறிகளும், ஒரே சேமிப்பக மதிப்பில் மூன்றையும் உள்ளடக்கும் வகையில் ஒதுக்கப்படுகின்றன (112+112+32=256).

1 uint public price0CumulativeLast;
2 uint public price1CumulativeLast;

இந்த மாறிகள் ஒவ்வொரு டோக்கனுக்கான ஒட்டுமொத்த செலவுகளைக் கொண்டுள்ளன (ஒவ்வொன்றும் மற்றொன்றின் அடிப்படையில்). ஒரு குறிப்பிட்ட காலப்பகுதியில் சராசரி பரிமாற்ற விகிதத்தைக் கணக்கிட இவற்றைப் பயன்படுத்தலாம்.

1 uint public kLast; // reserve0 * reserve1, மிகச் சமீபத்திய பணப்புழக்க நிகழ்வுக்குப் பிறகு உடனடியாக

token0 மற்றும் token1-க்கு இடையிலான பரிமாற்ற விகிதத்தை ஜோடிப் பரிமாற்றம் (pair exchange) தீர்மானிக்கும் விதம், வர்த்தகத்தின் போது இரண்டு இருப்புகளின் பெருக்கலை மாறிலியாக வைத்திருப்பதாகும். kLast என்பது இந்த மதிப்பாகும். பணப்புழக்கத்தை வழங்குபவர் டோக்கன்களை டெபாசிட் செய்யும்போது அல்லது திரும்பப் பெறும்போது இது மாறுகிறது, மேலும் 0.3% சந்தைக் கட்டணம் காரணமாக இது சற்று அதிகரிக்கிறது.

இதோ ஒரு எளிய உதாரணம். எளிமைக்காக அட்டவணையில் தசமப் புள்ளிக்குப் பிறகு மூன்று இலக்கங்கள் மட்டுமே உள்ளன என்பதையும், 0.3% வர்த்தகக் கட்டணத்தை நாங்கள் புறக்கணிக்கிறோம் என்பதையும் நினைவில் கொள்க, எனவே எண்கள் துல்லியமாக இல்லை.

நிகழ்வுreserve0reserve1reserve0 * reserve1சராசரி பரிமாற்ற விகிதம் (token1 / token0)
ஆரம்ப அமைப்பு1,000.0001,000.0001,000,000
வர்த்தகர் A 50 token0-ஐ 47.619 token1-க்கு மாற்றுகிறார்1,050.000952.3811,000,0000.952
வர்த்தகர் B 10 token0-ஐ 8.984 token1-க்கு மாற்றுகிறார்1,060.000943.3961,000,0000.898
வர்த்தகர் C 40 token0-ஐ 34.305 token1-க்கு மாற்றுகிறார்1,100.000909.0901,000,0000.858
வர்த்தகர் D 100 token1-ஐ 109.01 token0-க்கு மாற்றுகிறார்990.9901,009.0901,000,0000.917
வர்த்தகர் E 10 token0-ஐ 10.079 token1-க்கு மாற்றுகிறார்1,000.990999.0101,000,0001.008

வர்த்தகர்கள் அதிக token0-ஐ வழங்கும்போது, வழங்கல் மற்றும் தேவையின் அடிப்படையில் token1-இன் ஒப்பீட்டு மதிப்பு அதிகரிக்கிறது, மேலும் இதற்கு நேர்மாறாகவும் நடக்கும்.

லாக்

1 uint private unlocked = 1;

ரீஎன்ட்ரன்சி துஷ்பிரயோகத்தை (reentrancy abuse) (opens in a new tab) அடிப்படையாகக் கொண்ட ஒரு வகை பாதுகாப்பு பாதிப்புகள் உள்ளன. Uniswap தன்னிச்சையான ERC-20 டோக்கன்களைப் பரிமாற்ற வேண்டும், அதாவது அவற்றை அழைக்கும் Uniswap சந்தையைத் தவறாகப் பயன்படுத்த முயற்சிக்கும் ERC-20 ஒப்பந்தங்களை அழைப்பதாகும். ஒப்பந்தத்தின் ஒரு பகுதியாக unlocked மாறியைக் கொண்டிருப்பதன் மூலம், செயல்பாடுகள் இயங்கிக் கொண்டிருக்கும்போதே (அதே பரிவர்த்தனைக்குள்) அவை அழைக்கப்படுவதைத் தடுக்கலாம்.

1 modifier lock() {

இந்தச் செயல்பாடு ஒரு மாற்றியமைப்பான் (modifier) (opens in a new tab) ஆகும், இது ஒரு சாதாரண செயல்பாட்டைச் சுற்றி அதன் நடத்தையை ஏதேனும் ஒரு வகையில் மாற்றுவதற்கான ஒரு செயல்பாடாகும்.

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

unlocked ஒன்றிற்குச் சமமாக இருந்தால், அதை பூஜ்ஜியமாக அமைக்கவும். அது ஏற்கனவே பூஜ்ஜியமாக இருந்தால், அழைப்பைத் திருப்பி (revert), அதைத் தோல்வியடையச் செய்யவும்.

1 _;

ஒரு மாற்றியமைப்பானில் _; என்பது அசல் செயல்பாட்டு அழைப்பாகும் (அனைத்து அளவுருக்களுடனும்). இங்கே இதன் பொருள் என்னவென்றால், அது அழைக்கப்பட்டபோது unlocked ஒன்றாக இருந்தால் மட்டுமே செயல்பாட்டு அழைப்பு நிகழும், மேலும் அது இயங்கும் போது unlocked-இன் மதிப்பு பூஜ்ஜியமாக இருக்கும்.

1 unlocked = 1;
2 }

முக்கிய செயல்பாடு திரும்பிய பிறகு, லாக்கை விடுவிக்கவும்.

இதர செயல்பாடுகள்

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

இந்தச் செயல்பாடு அழைப்பாளர்களுக்குப் பரிமாற்றத்தின் தற்போதைய நிலையை வழங்குகிறது. Solidity செயல்பாடுகள் பல மதிப்புகளை வழங்க முடியும் (opens in a new tab) என்பதைக் கவனிக்கவும்.

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

இந்த உள் செயல்பாடு (internal function) பரிமாற்றத்திலிருந்து வேறு ஒருவருக்கு ஒரு குறிப்பிட்ட அளவு ERC20 டோக்கன்களை மாற்றுகிறது. நாம் அழைக்கும் செயல்பாடு transfer(address,uint) என்பதை SELECTOR குறிப்பிடுகிறது (மேலே உள்ள வரையறையைப் பார்க்கவும்).

டோக்கன் செயல்பாட்டிற்கான இடைமுகத்தை இறக்குமதி செய்வதைத் தவிர்க்க, ABI செயல்பாடுகளில் (opens in a new tab) ஒன்றைப் பயன்படுத்தி அழைப்பை "கைமுறையாக" உருவாக்குகிறோம்.

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

ERC-20 பரிமாற்ற அழைப்பு தோல்வியைப் புகாரளிக்க இரண்டு வழிகள் உள்ளன:

  1. ரிவர்ட் (Revert). வெளிப்புற ஒப்பந்தத்திற்கான அழைப்பு திரும்பினால், பூலியன் (boolean) திரும்பப் பெறும் மதிப்பு false ஆகும்
  2. சாதாரணமாக முடிவடையும் ஆனால் தோல்வியைப் புகாரளிக்கும். அந்த நிலையில் திரும்பப் பெறும் மதிப்பு பஃபர் (buffer) பூஜ்ஜியமற்ற நீளத்தைக் கொண்டுள்ளது, மேலும் பூலியன் மதிப்பாக டிகோட் செய்யப்படும்போது அது 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 to
8 );

ஒரு வர்த்தகர் ஒரு டோக்கனை மற்றொன்றுக்கு மாற்றும்போது இந்த நிகழ்வு வெளியிடப்படுகிறது. மீண்டும், அனுப்புநரும் சேருமிடமும் ஒன்றாக இருக்க வேண்டியதில்லை. ஒவ்வொரு டோக்கனும் பரிமாற்றத்திற்கு அனுப்பப்படலாம் அல்லது அதிலிருந்து பெறப்படலாம்.

1 event Sync(uint112 reserve0, uint112 reserve1);

இறுதியாக, காரணத்தைப் பொருட்படுத்தாமல், டோக்கன்கள் சேர்க்கப்படும் அல்லது திரும்பப் பெறப்படும் ஒவ்வொரு முறையும் சமீபத்திய இருப்புத் தகவலை (மற்றும் பரிமாற்ற விகிதத்தை) வழங்க Sync வெளியிடப்படுகிறது.

அமைவுச் செயல்பாடுகள்

புதிய ஜோடிப் பரிமாற்றம் அமைக்கப்படும்போது இந்தச் செயல்பாடுகள் ஒருமுறை அழைக்கப்பட வேண்டும்.

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

ஜோடியை உருவாக்கிய ஃபேக்டரியின் முகவரியை நாங்கள் கண்காணிப்போம் என்பதை கன்ஸ்ட்ரக்டர் (constructor) உறுதி செய்கிறது. initialize மற்றும் ஃபேக்டரி கட்டணத்திற்கு (ஒன்று இருந்தால்) இந்தத் தகவல் தேவைப்படுகிறது.

1 // வரிசைப்படுத்தப்படும் நேரத்தில் factory-ஆல் ஒரு முறை அழைக்கப்படுகிறது
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) {

கடந்த நேரம் பூஜ்ஜியமாக இல்லாவிட்டால், இந்த பிளாக்கில் நாமே முதல் பரிமாற்றப் பரிவர்த்தனை என்று அர்த்தம். அந்த நிலையில், நாம் செலவு அக்குமுலேட்டர்களைப் (cost accumulators) புதுப்பிக்க வேண்டும்.

1 // * ஒருபோதும் ஓவர்ஃப்ளோ ஆகாது, மேலும் + ஓவர்ஃப்ளோ விரும்பப்படுகிறது
2 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
3 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
4 }

ஒவ்வொரு செலவு அக்குமுலேட்டரும் சமீபத்திய செலவு (மற்ற டோக்கனின் இருப்பு/இந்த டோக்கனின் இருப்பு) மற்றும் கடந்த நேரத்தை விநாடிகளில் பெருக்கிப் புதுப்பிக்கப்படுகிறது. சராசரி விலையைப் பெற, நீங்கள் இரண்டு நேரப் புள்ளிகளில் ஒட்டுமொத்த விலையைப் படித்து, அவற்றுக்கிடையேயான நேர வித்தியாசத்தால் வகுக்க வேண்டும். எடுத்துக்காட்டாக, இந்த நிகழ்வுகளின் வரிசையைக் கருதுங்கள்:

நிகழ்வுreserve0reserve1நேரமுத்திரைவிளிம்புப் பரிமாற்ற விகிதம் (reserve1 / reserve0)price0CumulativeLast
ஆரம்ப அமைப்பு1,000.0001,000.0005,0001.0000
வர்த்தகர் A 50 token0-ஐ டெபாசிட் செய்து 47.619 token1-ஐத் திரும்பப் பெறுகிறார்1,050.000952.3815,0200.90720
வர்த்தகர் B 10 token0-ஐ டெபாசிட் செய்து 8.984 token1-ஐத் திரும்பப் பெறுகிறார்1,060.000943.3965,0300.89020+10*0.907 = 29.07
வர்த்தகர் C 40 token0-ஐ டெபாசிட் செய்து 34.305 token1-ஐத் திரும்பப் பெறுகிறார்1,100.000909.0905,1000.82629.07+70*0.890 = 91.37
வர்த்தகர் D 100 token1-ஐ டெபாசிட் செய்து 109.01 token0-ஐத் திரும்பப் பெறுகிறார்990.9901,009.0905,1101.01891.37+10*0.826 = 99.63
வர்த்தகர் E 10 token0-ஐ டெபாசிட் செய்து 10.079 token1-ஐத் திரும்பப் பெறுகிறார்1,000.990999.0105,1500.99899.63+40*1.1018 = 143.702

5,030 மற்றும் 5,150 நேரமுத்திரைகளுக்கு இடையில் Token0-இன் சராசரி விலையைக் கணக்கிட விரும்புகிறோம் என்று வைத்துக்கொள்வோம். price0Cumulative மதிப்பில் உள்ள வித்தியாசம் 143.702-29.07=114.632 ஆகும். இது இரண்டு நிமிடங்களில் (120 விநாடிகள்) உள்ள சராசரியாகும். எனவே சராசரி விலை 114.632/120 = 0.955 ஆகும்.

பழைய இருப்பு அளவுகளை நாம் தெரிந்துகொள்ள வேண்டியதற்குக் காரணம் இந்த விலைக் கணக்கீடுதான்.

1 reserve0 = uint112(balance0);
2 reserve1 = uint112(balance1);
3 blockTimestampLast = blockTimestamp;
4 emit Sync(reserve0, reserve1);
5 }

இறுதியாக, உலகளாவிய மாறிகளைப் (global variables) புதுப்பித்து, Sync நிகழ்வை வெளியிடவும்.

_mintFee
1 // கட்டணம் இயக்கப்பட்டிருந்தால், sqrt(k) வளர்ச்சியின் 1/6 பங்கிற்கு சமமான பணப்புழக்கத்தை உருவாக்கவும்
2 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {

Uniswap 2.0-இல் வர்த்தகர்கள் சந்தையைப் பயன்படுத்த 0.30% கட்டணம் செலுத்துகிறார்கள். அந்தக் கட்டணத்தின் பெரும்பகுதி (வர்த்தகத்தில் 0.25%) எப்போதும் பணப்புழக்கத்தை வழங்குபவர்களுக்குச் செல்கிறது. மீதமுள்ள 0.05% பணப்புழக்கத்தை வழங்குபவர்களுக்கோ அல்லது நெறிமுறைக் கட்டணமாக (protocol fee) ஃபேக்டரியால் குறிப்பிடப்பட்ட முகவரிக்கோ செல்லலாம், இது Uniswap-இன் மேம்பாட்டு முயற்சிக்காக அவர்களுக்குச் செலுத்தப்படுகிறது.

கணக்கீடுகளைக் குறைக்க (மற்றும் கேஸ் செலவுகளைக் குறைக்க), ஒவ்வொரு பரிவர்த்தனையிலும் அல்லாமல், பூலில் பணப்புழக்கம் சேர்க்கப்படும்போது அல்லது அகற்றப்படும்போது மட்டுமே இந்தக் கட்டணம் கணக்கிடப்படுகிறது.

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

ஃபேக்டரியின் கட்டணச் சேருமிடத்தைப் படிக்கவும். அது பூஜ்ஜியமாக இருந்தால், நெறிமுறைக் கட்டணம் இல்லை, அந்தக் கட்டணத்தைக் கணக்கிட வேண்டிய அவசியமும் இல்லை.

1 uint _kLast = kLast; // கேஸ் சேமிப்பு

kLast நிலை மாறி (state variable) சேமிப்பகத்தில் அமைந்துள்ளது, எனவே இது ஒப்பந்தத்திற்கான வெவ்வேறு அழைப்புகளுக்கு இடையில் ஒரு மதிப்பைக் கொண்டிருக்கும். ஒப்பந்தத்திற்கான செயல்பாட்டு அழைப்பு முடிவடையும் போது வெளியிடப்படும் தற்காலிக நினைவகத்தை (volatile memory) அணுகுவதை விடச் சேமிப்பகத்தை அணுகுவது மிகவும் செலவுமிக்கது, எனவே கேஸைச் சேமிக்க நாம் ஒரு உள் மாறியைப் பயன்படுத்துகிறோம்.

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) {

நெறிமுறைக் கட்டணத்தை வசூலிக்கப் புதிய பணப்புழக்கம் இருந்தால். வர்க்கமூலச் செயல்பாட்டை (square root function) இந்தக் கட்டுரையில் பின்னர் நீங்கள் காணலாம்.

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

கட்டணங்களின் இந்தச் சிக்கலான கணக்கீடு வெள்ளைத்தாளில் (whitepaper) (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-ஐ பூஜ்ஜியமாக அமைக்கவும் (அது ஏற்கனவே அப்படி இல்லை என்றால்). இந்த ஒப்பந்தம் எழுதப்பட்டபோது, தங்களுக்குத் தேவையில்லாத சேமிப்பகத்தைப் பூஜ்ஜியமாக்குவதன் மூலம் Ethereum நிலையின் ஒட்டுமொத்த அளவைக் குறைக்க ஒப்பந்தங்களை ஊக்குவிக்கும் கேஸ் பணத்தைத் திரும்பப்பெறும் அம்சம் (gas refund feature) (opens in a new tab) இருந்தது. சாத்தியமான போதெல்லாம் இந்தக் குறியீடு அந்தப் பணத்தைத் திரும்பப் பெறுகிறது.

வெளிப்புறமாக அணுகக்கூடிய செயல்பாடுகள்

எந்தவொரு பரிவர்த்தனை அல்லது ஒப்பந்தமும் இந்தச் செயல்பாடுகளை அழைக்க முடியும் என்றாலும், அவை பெரிஃபெரி (periphery) ஒப்பந்தத்திலிருந்து அழைக்கப்படும் வகையில் வடிவமைக்கப்பட்டுள்ளன என்பதைக் கவனத்தில் கொள்ளவும். நீங்கள் அவற்றை நேரடியாக அழைத்தால், ஜோடிப் பரிமாற்றத்தை உங்களால் ஏமாற்ற முடியாது, ஆனால் ஒரு தவறு மூலம் நீங்கள் மதிப்பை இழக்க நேரிடலாம்.

mint
1 // முக்கியமான பாதுகாப்புச் சரிபார்ப்புகளைச் செய்யும் ஒரு ஒப்பந்தத்திலிருந்து இந்த குறைந்த-நிலை செயல்பாடு அழைக்கப்பட வேண்டும்
2 function mint(address to) external lock returns (uint liquidity) {

பணப்புழக்கத்தை வழங்குபவர் பூலில் பணப்புழக்கத்தைச் சேர்க்கும்போது இந்தச் செயல்பாடு அழைக்கப்படுகிறது. இது வெகுமதியாகக் கூடுதல் பணப்புழக்க டோக்கன்களை உருவாக்குகிறது. அதே பரிவர்த்தனையில் பணப்புழக்கத்தைச் சேர்த்த பிறகு அதை அழைக்கும் ஒரு பெரிஃபெரி ஒப்பந்தத்திலிருந்து இது அழைக்கப்பட வேண்டும் (எனவே முறையான உரிமையாளருக்கு முன்பு புதிய பணப்புழக்கத்தைக் கோரும் பரிவர்த்தனையை வேறு எவராலும் சமர்ப்பிக்க முடியாது).

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // கேஸ் சேமிப்பு

பல மதிப்புகளை வழங்கும் Solidity செயல்பாட்டின் முடிவுகளைப் படிக்கும் முறை இதுவாகும். கடைசியாகத் திரும்பப் பெற்ற மதிப்புகளான பிளாக் நேரமுத்திரையை நாங்கள் நிராகரிக்கிறோம், ஏனெனில் அது எங்களுக்குத் தேவையில்லை.

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

தற்போதைய இருப்புகளைப் பெற்று, ஒவ்வொரு டோக்கன் வகையிலும் எவ்வளவு சேர்க்கப்பட்டுள்ளது என்பதைப் பார்க்கவும்.

1 bool feeOn = _mintFee(_reserve0, _reserve1);

வசூலிக்க வேண்டிய நெறிமுறைக் கட்டணங்கள் ஏதேனும் இருந்தால் அவற்றைக் கணக்கிட்டு, அதற்கேற்ப பணப்புழக்க டோக்கன்களை உருவாக்கவும். _mintFee-க்கான அளவுருக்கள் பழைய இருப்பு மதிப்புகள் என்பதால், கட்டணங்கள் காரணமாக ஏற்படும் பூல் மாற்றங்களின் அடிப்படையில் மட்டுமே கட்டணம் துல்லியமாகக் கணக்கிடப்படுகிறது.

1 uint _totalSupply = totalSupply; // கேஸ் சேமிப்பு, _mintFee-ல் totalSupply புதுப்பிக்கப்படலாம் என்பதால் இது இங்கே வரையறுக்கப்பட வேண்டும்
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 ஆகும். இது அதிகச் செலவு இல்லை.

முதல் டெபாசிட் நேரத்தில் இரண்டு டோக்கன்களின் ஒப்பீட்டு மதிப்பு எங்களுக்குத் தெரியாது, எனவே டெபாசிட் இரண்டு டோக்கன்களிலும் சமமான மதிப்பை எங்களுக்கு வழங்குகிறது என்று கருதி, நாங்கள் தொகைகளைப் பெருக்கி வர்க்கமூலத்தை எடுக்கிறோம்.

ஆர்பிட்ரேஜ் (arbitrage) மூலம் மதிப்பை இழப்பதைத் தவிர்க்க, சமமான மதிப்பை வழங்குவது டெபாசிட் செய்பவரின் நலனுக்காக இருப்பதால் இதை நாம் நம்பலாம். இரண்டு டோக்கன்களின் மதிப்பும் ஒன்றுதான் என்று வைத்துக்கொள்வோம், ஆனால் எங்கள் டெபாசிட் செய்பவர் Token0-ஐ விட நான்கு மடங்கு அதிகமாக Token1-ஐ டெபாசிட் செய்துள்ளார். ஜோடிப் பரிமாற்றம் Token0-ஐ அதிக மதிப்புமிக்கதாகக் கருதுகிறது என்ற உண்மையைப் பயன்படுத்தி ஒரு வர்த்தகர் அதிலிருந்து மதிப்பை எடுக்க முடியும்.

நிகழ்வுreserve0reserve1reserve0 * reserve1பூலின் மதிப்பு (reserve0 + reserve1)
ஆரம்ப அமைப்பு83225640
வர்த்தகர் 8 Token0 டோக்கன்களை டெபாசிட் செய்து, 16 Token1-ஐத் திரும்பப் பெறுகிறார்161625632

நீங்கள் பார்க்கிறபடி, வர்த்தகர் கூடுதலாக 8 டோக்கன்களைப் பெற்றார், இது பூலின் மதிப்பு குறைவதிலிருந்து வருகிறது, இது அதைச் சொந்தமாகக் கொண்ட டெபாசிட் செய்பவரைப் பாதிக்கிறது.

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

அடுத்தடுத்த ஒவ்வொரு டெபாசிட்டிலும் இரண்டு சொத்துக்களுக்கு இடையிலான பரிமாற்ற விகிதத்தை நாங்கள் ஏற்கனவே அறிவோம், மேலும் பணப்புழக்கத்தை வழங்குபவர்கள் இரண்டிலும் சமமான மதிப்பை வழங்குவார்கள் என்று நாங்கள் எதிர்பார்க்கிறோம். அவர்கள் அவ்வாறு செய்யவில்லை என்றால், ஒரு தண்டனையாக அவர்கள் வழங்கிய குறைந்த மதிப்பின் அடிப்படையில் அவர்களுக்குப் பணப்புழக்க டோக்கன்களை வழங்குகிறோம்.

இது ஆரம்ப டெபாசிட்டாக இருந்தாலும் சரி அல்லது அடுத்தடுத்ததாக இருந்தாலும் சரி, நாங்கள் வழங்கும் பணப்புழக்க டோக்கன்களின் எண்ணிக்கை reserve0*reserve1-இல் ஏற்படும் மாற்றத்தின் வர்க்கமூலத்திற்குச் சமம் மற்றும் பணப்புழக்க டோக்கனின் மதிப்பு மாறாது (இரண்டு வகைகளிலும் சமமான மதிப்புகளைக் கொண்டிருக்காத டெபாசிட்டை நாங்கள் பெற்றாலொழிய, அந்த நிலையில் "அபராதம்" விநியோகிக்கப்படும்). ஒரே மதிப்பைக் கொண்ட இரண்டு டோக்கன்களுடன், மூன்று நல்ல டெபாசிட்டுகள் மற்றும் ஒரு மோசமான டெபாசிட் (ஒரு டோக்கன் வகையின் டெபாசிட் மட்டுமே, எனவே இது எந்தப் பணப்புழக்க டோக்கன்களையும் உருவாக்காது) கொண்ட மற்றொரு உதாரணம் இங்கே.

நிகழ்வுreserve0reserve1reserve0 * reserve1பூல் மதிப்பு (reserve0 + reserve1)இந்த டெபாசிட்டிற்காக உருவாக்கப்பட்ட பணப்புழக்க டோக்கன்கள்மொத்த பணப்புழக்க டோக்கன்கள்ஒவ்வொரு பணப்புழக்க டோக்கனின் மதிப்பு
ஆரம்ப அமைப்பு8.0008.0006416.000882.000
ஒவ்வொரு வகையிலும் நான்கை டெபாசிட் செய்யவும்12.00012.00014424.0004122.000
ஒவ்வொரு வகையிலும் இரண்டை டெபாசிட் செய்யவும்14.00014.00019628.0002142.000
சமமற்ற மதிப்பு டெபாசிட்18.00014.00025232.000014~2.286
ஆர்பிட்ரேஜுக்குப் பிறகு~15.874~15.874252~31.748014~2.267
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) புதுப்பித்து, பொருத்தமான நிகழ்வை வெளியிடவும்.

burn
1 // முக்கியமான பாதுகாப்புச் சரிபார்ப்புகளைச் செய்யும் ஒரு ஒப்பந்தத்திலிருந்து இந்த குறைந்த-நிலை செயல்பாடு அழைக்கப்பட வேண்டும்
2 function burn(address to) external lock returns (uint amount0, uint amount1) {

பணப்புழக்கம் திரும்பப் பெறப்படும்போது மற்றும் பொருத்தமான பணப்புழக்க டோக்கன்கள் எரிக்கப்பட வேண்டியிருக்கும் போது இந்தச் செயல்பாடு அழைக்கப்படுகிறது. இது ஒரு பெரிஃபெரி கணக்கிலிருந்தும் அழைக்கப்பட வேண்டும்.

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // கேஸ் சேமிப்பு
2 address _token0 = token0; // கேஸ் சேமிப்பு
3 address _token1 = token1; // கேஸ் சேமிப்பு
4 uint balance0 = IERC20(_token0).balanceOf(address(this));
5 uint balance1 = IERC20(_token1).balanceOf(address(this));
6 uint liquidity = balanceOf[address(this)];

அழைப்பிற்கு முன்பு எரிக்கப்பட வேண்டிய பணப்புழக்கத்தைப் பெரிஃபெரி ஒப்பந்தம் இந்த ஒப்பந்தத்திற்கு மாற்றியது. அந்த வகையில் எவ்வளவு பணப்புழக்கத்தை எரிக்க வேண்டும் என்பதை நாங்கள் அறிவோம், மேலும் அது எரிக்கப்படுவதை நாங்கள் உறுதிசெய்ய முடியும்.

1 bool feeOn = _mintFee(_reserve0, _reserve1);
2 uint _totalSupply = totalSupply; // கேஸ் சேமிப்பு, _mintFee-ல் totalSupply புதுப்பிக்கப்படலாம் என்பதால் இது இங்கே வரையறுக்கப்பட வேண்டும்
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 செயல்பாட்டின் கண்ணாடிப் பிம்பமாகும்.

swap
1 // முக்கியமான பாதுகாப்புச் சரிபார்ப்புகளைச் செய்யும் ஒரு ஒப்பந்தத்திலிருந்து இந்த குறைந்த-நிலை செயல்பாடு அழைக்கப்பட வேண்டும்
2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {

இந்தச் செயல்பாடும் ஒரு பெரிஃபெரி ஒப்பந்தத்திலிருந்து அழைக்கப்பட வேண்டும்.

1 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
2 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // கேஸ் சேமிப்பு
3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
4
5 uint balance0;
6 uint balance1;
7 { // _token{0,1}-க்கான ஸ்கோப், 'stack too deep' பிழைகளைத் தவிர்க்கிறது

உள்ளூர் மாறிகளை (local variables) நினைவகத்தில் சேமிக்கலாம் அல்லது அவை அதிகமாக இல்லாவிட்டால் நேரடியாக ஸ்டாக்கில் (stack) சேமிக்கலாம். நாம் ஸ்டாக்கைப் பயன்படுத்தும் வகையில் எண்ணிக்கையைக் கட்டுப்படுத்த முடிந்தால், நாம் குறைவான கேஸைப் பயன்படுத்துவோம். மேலும் விவரங்களுக்கு மஞ்சள் தாள், முறையான Ethereum விவரக்குறிப்புகளைப் (yellow paper, the formal Ethereum specifications) (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); // நம்பிக்கையுடன் டோக்கன்களைப் பரிமாற்றவும்

இந்த இடமாற்றம் நம்பிக்கையானது (optimistic), ஏனென்றால் அனைத்து நிபந்தனைகளும் பூர்த்தி செய்யப்பட்டுள்ளன என்பதை நாம் உறுதிசெய்வதற்கு முன்பே இடமாற்றம் செய்கிறோம். Ethereum-இல் இது பரவாயில்லை, ஏனென்றால் அழைப்பில் பின்னர் நிபந்தனைகள் பூர்த்தி செய்யப்படாவிட்டால், அதிலிருந்தும் அது உருவாக்கிய எந்த மாற்றங்களிலிருந்தும் நாம் ரிவர்ட் செய்கிறோம்.

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

கோரப்பட்டால் பெறுநருக்கு ஸ்வாப் (swap) பற்றித் தெரிவிக்கவும்.

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

தற்போதைய இருப்புகளைப் பெறவும். ஸ்வாப்பிற்காக எங்களை அழைப்பதற்கு முன்பு பெரிஃபெரி ஒப்பந்தம் எங்களுக்கு டோக்கன்களை அனுப்புகிறது. இது ஏமாற்றப்படவில்லை என்பதை ஒப்பந்தம் சரிபார்க்க எளிதாக்குகிறது, இந்தச் சரிபார்ப்பு முக்கிய ஒப்பந்தத்தில் நடக்க வேண்டும் (ஏனெனில் எங்கள் பெரிஃபெரி ஒப்பந்தத்தைத் தவிர வேறு நிறுவனங்களாலும் நாங்கள் அழைக்கப்படலாம்).

1 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
2 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
3 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
4 { // reserve{0,1}Adjusted-க்கான ஸ்கோப், 'stack too deep' பிழைகளைத் தவிர்க்கிறது
5 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
6 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
7 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');

ஸ்வாப்பிலிருந்து நாம் இழக்கவில்லை என்பதை உறுதி செய்வதற்கான ஒரு சானிட்டி செக் (sanity check) இதுவாகும். எந்தச் சூழ்நிலையிலும் ஒரு ஸ்வாப் reserve0*reserve1-ஐக் குறைக்கக் கூடாது. ஸ்வாப்பில் 0.3% கட்டணம் அனுப்பப்படுவதை நாம் உறுதி செய்யும் இடமும் இதுதான்; K-இன் மதிப்பைச் சானிட்டி செக் செய்வதற்கு முன்பு, நாம் இரண்டு இருப்புகளையும் 1000-ஆல் பெருக்கி, தொகைகளை 3-ஆல் பெருக்கிக் கழிக்கிறோம், அதாவது அதன் K மதிப்பைத் தற்போதைய இருப்புகளின் K மதிப்புடன் ஒப்பிடுவதற்கு முன்பு இருப்பிலிருந்து 0.3% (3/1000 = 0.003 = 0.3%) கழிக்கப்படுகிறது.

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

reserve0 மற்றும் reserve1-ஐப் புதுப்பிக்கவும், தேவைப்பட்டால் விலை அக்குமுலேட்டர்கள் மற்றும் நேரமுத்திரையைப் புதுப்பித்து ஒரு நிகழ்வை வெளியிடவும்.

Sync அல்லது Skim

ஜோடிப் பரிமாற்றம் தன்னிடம் இருப்பதாக நினைக்கும் இருப்புகளுடன் உண்மையான இருப்புகள் ஒத்திசைவில்லாமல் போக வாய்ப்புள்ளது. ஒப்பந்தத்தின் அனுமதியின்றி டோக்கன்களைத் திரும்பப் பெற வழி இல்லை, ஆனால் டெபாசிட்டுகள் வேறு விஷயம். ஒரு கணக்கு mint அல்லது swap-ஐ அழைக்காமலேயே பரிமாற்றத்திற்கு டோக்கன்களை மாற்ற முடியும்.

அந்த நிலையில் இரண்டு தீர்வுகள் உள்ளன:

  • sync, இருப்புகளைத் தற்போதைய இருப்புகளுக்குப் புதுப்பிக்கவும்
  • skim, கூடுதல் தொகையைத் திரும்பப் பெறவும். டோக்கன்களை யார் டெபாசிட் செய்தார்கள் என்பது எங்களுக்குத் தெரியாததால், எந்தவொரு கணக்கும் skim-ஐ அழைக்க அனுமதிக்கப்படுகிறது என்பதை நினைவில் கொள்க. இந்தத் தகவல் ஒரு நிகழ்வில் வெளியிடப்படுகிறது, ஆனால் பிளாக்செயினிலிருந்து நிகழ்வுகளை அணுக முடியாது.
1 // இருப்புகளுடன் பொருந்துமாறு பேலன்ஸ்களைக் கட்டாயப்படுத்தவும்
2 function skim(address to) external lock {
3 address _token0 = token0; // கேஸ் சேமிப்பு
4 address _token1 = token1; // கேஸ் சேமிப்பு
5 _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
6 _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
7 }
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 டோக்கன்களின் அடிப்படையில் ஒரு ஜோடிப் பரிமாற்ற ஒப்பந்தத்தை அடையாளம் காணும் ஒரு மேப்பிங் (mapping) ஆகும். ERC-20 டோக்கன்கள் அவற்றைச் செயல்படுத்தும் ஒப்பந்தங்களின் முகவரிகளால் அடையாளம் காணப்படுகின்றன, எனவே விசைகள் (keys) மற்றும் மதிப்பு அனைத்தும் முகவரிகளாகும். tokenA-லிருந்து tokenB-க்கு மாற்ற உங்களை அனுமதிக்கும் ஜோடிப் பரிமாற்றத்தின் முகவரியைப் பெற, நீங்கள் getPair[<tokenA address>][<tokenB address>]-ஐப் பயன்படுத்துகிறீர்கள் (அல்லது வேறு வழியில்).

இரண்டாவது மாறி, allPairs, இது இந்த ஃபேக்டரியால் உருவாக்கப்பட்ட ஜோடிப் பரிமாற்றங்களின் அனைத்து முகவரிகளையும் உள்ளடக்கிய ஒரு வரிசையாகும் (array). Ethereum-இல் நீங்கள் ஒரு மேப்பிங்கின் உள்ளடக்கத்தை மீண்டும் மீண்டும் செய்ய முடியாது (iterate), அல்லது அனைத்து விசைகளின் பட்டியலையும் பெற முடியாது, எனவே இந்த ஃபேக்டரி எந்தப் பரிமாற்றங்களை நிர்வகிக்கிறது என்பதை அறிய இந்த மாறி மட்டுமே ஒரே வழியாகும்.

குறிப்பு: ஒரு மேப்பிங்கின் அனைத்து விசைகளையும் உங்களால் மீண்டும் மீண்டும் செய்ய முடியாததற்குக் காரணம், ஒப்பந்தத் தரவுச் சேமிப்பகம் செலவுமிக்கது, எனவே நாம் அதை எவ்வளவு குறைவாகப் பயன்படுத்துகிறோமோ அவ்வளவு நல்லது, மேலும் அதை எவ்வளவு குறைவாக மாற்றுகிறோமோ அவ்வளவு நல்லது. நீங்கள் மறு செய்கையை ஆதரிக்கும் மேப்பிங்குகளை (mappings that support iteration) (opens in a new tab) உருவாக்கலாம், ஆனால் அவற்றுக்கு விசைகளின் பட்டியலுக்குக் கூடுதல் சேமிப்பகம் தேவை. பெரும்பாலான பயன்பாடுகளில் உங்களுக்கு அது தேவையில்லை.

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

புதிய ஜோடிப் பரிமாற்றம் உருவாக்கப்படும்போது இந்த நிகழ்வு வெளியிடப்படுகிறது. இதில் டோக்கன்களின் முகவரிகள், ஜோடிப் பரிமாற்றத்தின் முகவரி மற்றும் ஃபேக்டரியால் நிர்வகிக்கப்படும் மொத்தப் பரிமாற்றங்களின் எண்ணிக்கை ஆகியவை அடங்கும்.

1 constructor(address _feeToSetter) public {
2 feeToSetter = _feeToSetter;
3 }

கன்ஸ்ட்ரக்டர் செய்யும் ஒரே விஷயம் feeToSetter-ஐக் குறிப்பிடுவதுதான். ஃபேக்டரிகள் கட்டணம் இல்லாமல் தொடங்குகின்றன, மேலும் feeSetter மட்டுமே அதை மாற்ற முடியும்.

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

இந்தச் செயல்பாடு பரிமாற்ற ஜோடிகளின் எண்ணிக்கையை வழங்குகிறது.

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

இரண்டு ERC-20 டோக்கன்களுக்கு இடையே ஒரு ஜோடிப் பரிமாற்றத்தை உருவாக்குவது ஃபேக்டரியின் முக்கியச் செயல்பாடாகும். இந்தச் செயல்பாட்டை யார் வேண்டுமானாலும் அழைக்கலாம் என்பதை நினைவில் கொள்க. புதிய ஜோடிப் பரிமாற்றத்தை உருவாக்க உங்களுக்கு Uniswap-இடமிருந்து அனுமதி தேவையில்லை.

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

புதிய பரிமாற்றத்தின் முகவரி தீர்மானிக்கக்கூடியதாக இருக்க வேண்டும் என்று நாங்கள் விரும்புகிறோம், எனவே அதை ஆஃப்செயினில் (offchain) முன்கூட்டியே கணக்கிடலாம் (இது லேயர் 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 பைட்கோடை (bytecode) நினைவகத்தில் எழுதும் குறியீடு ஆகிய இரண்டும்). பொதுவாக Solidity-இல் நாம் addr = new <name of contract>(<constructor parameters>)-ஐப் பயன்படுத்துகிறோம், மேலும் கம்பைலர் (compiler) எல்லாவற்றையும் நமக்காகக் கவனித்துக்கொள்கிறது, ஆனால் ஒரு தீர்மானிக்கக்கூடிய ஒப்பந்த முகவரியைக் கொண்டிருக்க நாம் CREATE2 ஆப்கோடைப் (opcode) (opens in a new tab) பயன்படுத்த வேண்டும். இந்தக் குறியீடு எழுதப்பட்டபோது அந்த ஆப்கோட் Solidity-ஆல் இன்னும் ஆதரிக்கப்படவில்லை, எனவே குறியீட்டைக் கைமுறையாகப் பெற வேண்டியிருந்தது. இது இனி ஒரு பிரச்சனையல்ல, ஏனெனில் Solidity இப்போது CREATE2-ஐ ஆதரிக்கிறது (opens in a new tab).

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

ஒரு ஆப்கோட் Solidity-ஆல் இன்னும் ஆதரிக்கப்படாதபோது, இன்லைன் அசெம்பிளியைப் (inline assembly) (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 பணப்புழக்க டோக்கனைச் செயல்படுத்துகிறது. இது OpenZeppelin ERC-20 ஒப்பந்தத்தைப் போன்றது, எனவே வேறுபட்ட பகுதியான permit செயல்பாட்டை மட்டுமே நான் விளக்குவேன்.

Ethereum-இல் பரிவர்த்தனைகளுக்கு ஈதர் (ETH) செலவாகும், இது உண்மையான பணத்திற்குச் சமம். உங்களிடம் ERC-20 டோக்கன்கள் இருந்து ETH இல்லையென்றால், உங்களால் பரிவர்த்தனைகளை அனுப்ப முடியாது, எனவே உங்களால் அவற்றை வைத்து எதுவும் செய்ய முடியாது. இந்தப் பிரச்சனையைத் தவிர்ப்பதற்கான ஒரு தீர்வு மெட்டா-பரிவர்த்தனைகள் (meta-transactions) (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;

இந்த ஹாஷ் (hash) என்பது பரிவர்த்தனை வகைக்கான அடையாளங்காட்டி (identifier for the transaction type) (opens in a new tab) ஆகும். இந்த அளவுருக்களுடன் Permit-ஐ மட்டுமே நாங்கள் இங்கே ஆதரிக்கிறோம்.

1 mapping(address => uint) public nonces;

ஒரு பெறுநர் டிஜிட்டல் கையொப்பத்தைப் போலியாக உருவாக்குவது சாத்தியமில்லை. இருப்பினும், அதே பரிவர்த்தனையை இரண்டு முறை அனுப்புவது எளிதானது (இது ரீப்ளே தாக்குதலின் (replay attack) (opens in a new tab) ஒரு வடிவமாகும்). இதைத் தடுக்க, நாங்கள் ஒரு நான்ஸைப் (nonce) (opens in a new tab) பயன்படுத்துகிறோம். புதிய Permit-இன் நான்ஸ் கடைசியாகப் பயன்படுத்தப்பட்டதை விட ஒன்று அதிகமாக இல்லாவிட்டால், அது செல்லுபடியாகாது என்று நாங்கள் கருதுகிறோம்.

1 constructor() public {
2 uint chainId;
3 assembly {
4 chainId := chainid
5 }

இது செயின் அடையாளங்காட்டியைக் (chain identifier) (opens in a new tab) மீட்டெடுப்பதற்கான குறியீடாகும். இது Yul (opens in a new tab) எனப்படும் EVM அசெம்பிளி பேச்சுவழக்கைப் பயன்படுத்துகிறது. Yul-இன் தற்போதைய பதிப்பில் நீங்கள் chainid()-ஐப் பயன்படுத்த வேண்டும், chainid-ஐ அல்ல என்பதை நினைவில் கொள்க.

1 DOMAIN_SEPARATOR = keccak256(
2 abi.encode(
3 keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
4 keccak256(bytes(name)),
5 keccak256(bytes('1')),
6 chainId,
7 address(this)
8 )
9 );
10 }

EIP-712-க்கான டொமைன் செப்பரேட்டரைக் (domain separator) (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) மூன்று ஸ்கேலார் (scalar) மதிப்புகளையும் (v, r மற்றும் s) அளவுருக்களாகப் பெறுகிறது.

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

கெடுவிற்குப் பிறகு பரிவர்த்தனைகளை ஏற்க வேண்டாம்.

1 bytes32 digest = keccak256(
2 abi.encodePacked(
3 '\x19\x01',
4 DOMAIN_SEPARATOR,
5 keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
6 )
7 );

abi.encodePacked(...) என்பது நாங்கள் பெற எதிர்பார்க்கும் செய்தியாகும். நான்ஸ் என்னவாக இருக்க வேண்டும் என்பது எங்களுக்குத் தெரியும், எனவே அதை ஒரு அளவுருவாகப் பெற வேண்டிய அவசியமில்லை.

Ethereum கையொப்ப அல்காரிதம் கையொப்பமிட 256 பிட்களைப் பெற எதிர்பார்க்கிறது, எனவே நாங்கள் keccak256 ஹாஷ் செயல்பாட்டைப் பயன்படுத்துகிறோம்.

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

டைஜஸ்ட் (digest) மற்றும் கையொப்பத்திலிருந்து 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 அங்கீகாரமாகக் (approve) (opens in a new tab) கருதவும்.

பெரிஃபெரி ஒப்பந்தங்கள் (The Periphery Contracts)

பெரிஃபெரி (periphery) ஒப்பந்தங்கள் Uniswap-க்கான API (பயன்பாட்டு நிரல் இடைமுகம்) ஆகும். பிற ஒப்பந்தங்கள் அல்லது பரவலாக்கப்பட்ட பயன்பாடுகளிலிருந்து (dapps) வெளிப்புற அழைப்புகளுக்கு இவை கிடைக்கின்றன. நீங்கள் கோர் (core) ஒப்பந்தங்களை நேரடியாக அழைக்கலாம், ஆனால் அது மிகவும் சிக்கலானது மற்றும் நீங்கள் தவறு செய்தால் மதிப்பை இழக்க நேரிடும். கோர் ஒப்பந்தங்கள் ஏமாற்றப்படாமல் இருப்பதை உறுதி செய்வதற்கான சோதனைகளை மட்டுமே கொண்டுள்ளன, மற்றவர்களுக்கான சரிபார்ப்புகளை அல்ல. அவை பெரிஃபெரியில் உள்ளன, எனவே தேவைக்கேற்ப அவற்றைப் புதுப்பிக்க முடியும்.

UniswapV2Router01.sol

இந்த ஒப்பந்தத்தில் (opens in a new tab) சிக்கல்கள் உள்ளன, மேலும் இனி பயன்படுத்தப்படக்கூடாது (opens in a new tab). அதிர்ஷ்டவசமாக, பெரிஃபெரி ஒப்பந்தங்கள் நிலையற்றவை (stateless) மற்றும் எந்த சொத்துக்களையும் கொண்டிருக்கவில்லை, எனவே அதை நிராகரிப்பது மற்றும் அதற்குப் பதிலாக UniswapV2Router02 ஐப் பயன்படுத்த மக்களைப் பரிந்துரைப்பது எளிது.

UniswapV2Router02.sol

பெரும்பாலான சந்தர்ப்பங்களில் நீங்கள் இந்த ஒப்பந்தத்தின் (opens in a new tab) மூலம் Uniswap ஐப் பயன்படுத்துவீர்கள். அதை எவ்வாறு பயன்படுத்துவது என்பதை இங்கே (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 ஆகும். Uniswap v2 எந்தவொரு ERC-20 டோக்கன்களின் ஜோடிக்கும் பரிமாற்றங்களை அனுமதிக்கிறது, ஆனால் ஈதர் (ETH) ஒரு ERC-20 டோக்கன் அல்ல. இது தரநிலைக்கு முந்தையது மற்றும் தனித்துவமான வழிமுறைகளால் மாற்றப்படுகிறது. ERC-20 டோக்கன்களுக்குப் பொருந்தும் ஒப்பந்தங்களில் ETH-ஐப் பயன்படுத்துவதை சாத்தியமாக்க, மக்கள் ராப்ட் ஈதர் (WETH) (opens in a new tab) ஒப்பந்தத்தைக் கொண்டு வந்தனர். நீங்கள் இந்த ஒப்பந்தத்திற்கு ETH-ஐ அனுப்புகிறீர்கள், அது உங்களுக்கு சமமான அளவு WETH-ஐ உருவாக்குகிறது (mints). அல்லது நீங்கள் WETH-ஐ எரித்துவிட்டு (burn), ETH-ஐ திரும்பப் பெறலாம்.

1contract UniswapV2Router02 is IUniswapV2Router02 {
2 using SafeMath for uint;
3
4 address public immutable override factory;
5 address public immutable override WETH;

எந்த ஃபேக்டரியைப் (factory) பயன்படுத்த வேண்டும் என்பதையும், WETH தேவைப்படும் பரிவர்த்தனைகளுக்கு எந்த WETH ஒப்பந்தத்தைப் பயன்படுத்த வேண்டும் என்பதையும் திசைவி (router) தெரிந்து கொள்ள வேண்டும். இந்த மதிப்புகள் மாற்ற முடியாதவை (immutable) (opens in a new tab), அதாவது அவற்றை கன்ஸ்ட்ரக்டரில் (constructor) மட்டுமே அமைக்க முடியும். இது குறைவான நேர்மையான ஒப்பந்தங்களைச் சுட்டிக்காட்டும் வகையில் அவற்றை யாராலும் மாற்ற முடியாது என்ற நம்பிக்கையை பயனர்களுக்கு அளிக்கிறது.

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

இந்த மாடிஃபையர் (modifier) நேர வரம்புக்குட்பட்ட பரிவர்த்தனைகள் ("உங்களால் முடிந்தால் Y நேரத்திற்கு முன் X-ஐச் செய்யுங்கள்") அவற்றின் நேர வரம்பிற்குப் பிறகு நடக்காது என்பதை உறுதி செய்கிறது.

1 constructor(address _factory, address _WETH) public {
2 factory = _factory;
3 WETH = _WETH;
4 }

கன்ஸ்ட்ரக்டர் மாற்ற முடியாத நிலை மாறிகளை (immutable state variables) மட்டுமே அமைக்கிறது.

1 receive() external payable {
2 assert(msg.sender == WETH); // WETH ஒப்பந்தத்திலிருந்து ஃபால்பேக் மூலம் மட்டுமே ETH-ஐ ஏற்கவும்
3 }

WETH ஒப்பந்தத்திலிருந்து டோக்கன்களை மீண்டும் ETH ஆக மாற்றும்போது இந்தச் செயல்பாடு (function) அழைக்கப்படுகிறது. நாம் பயன்படுத்தும் WETH ஒப்பந்தத்திற்கு மட்டுமே அதைச் செய்ய அதிகாரம் உள்ளது.

பணப்புழக்கத்தைச் சேர்த்தல்

இந்தச் செயல்பாடுகள் ஜோடி பரிமாற்றத்தில் (pair exchange) டோக்கன்களைச் சேர்க்கின்றன, இது பணப்புழக்கக் குழுவை (liquidity pool) அதிகரிக்கிறது.

1
2 // **** பணப்புழக்கத்தை சேர்க்கவும் ****
3 function _addLiquidity(

ஜோடி பரிமாற்றத்தில் டெபாசிட் செய்ய வேண்டிய A மற்றும் B டோக்கன்களின் அளவைக் கணக்கிட இந்தச் செயல்பாடு பயன்படுத்தப்படுகிறது.

1 address tokenA,
2 address tokenB,

இவை ERC-20 டோக்கன் ஒப்பந்தங்களின் முகவரிகள்.

1 uint amountADesired,
2 uint amountBDesired,

இவை பணப்புழக்கத்தை வழங்குபவர் (liquidity provider) டெபாசிட் செய்ய விரும்பும் தொகைகள். அவை டெபாசிட் செய்யப்பட வேண்டிய A மற்றும் B-இன் அதிகபட்ச தொகைகளாகும்.

1 uint amountAMin,
2 uint amountBMin

இவை டெபாசிட் செய்ய ஏற்றுக்கொள்ளக்கூடிய குறைந்தபட்ச தொகைகள். இந்தத் தொகைகள் அல்லது அதற்கு மேற்பட்ட தொகைகளுடன் பரிவர்த்தனை நடைபெற முடியாவிட்டால், அதிலிருந்து வெளியேறவும் (revert). இந்த அம்சம் உங்களுக்கு வேண்டாம் என்றால், பூஜ்ஜியத்தைக் குறிப்பிடவும்.

பணப்புழக்கத்தை வழங்குபவர்கள் பொதுவாக குறைந்தபட்சத்தைக் குறிப்பிடுகிறார்கள், ஏனெனில் அவர்கள் பரிவர்த்தனையை தற்போதைய பரிமாற்ற விகிதத்திற்கு நெருக்கமானதாகக் கட்டுப்படுத்த விரும்புகிறார்கள். பரிமாற்ற விகிதம் அதிகமாக ஏற்ற இறக்கமாக இருந்தால், அது அடிப்படை மதிப்புகளை மாற்றும் செய்திகளைக் குறிக்கலாம், மேலும் அவர்கள் என்ன செய்ய வேண்டும் என்பதை கைமுறையாக முடிவு செய்ய விரும்புகிறார்கள்.

எடுத்துக்காட்டாக, பரிமாற்ற விகிதம் ஒன்றுக்கு ஒன்று (one to one) என இருக்கும் ஒரு சூழ்நிலையை கற்பனை செய்து பாருங்கள், மேலும் பணப்புழக்கத்தை வழங்குபவர் இந்த மதிப்புகளைக் குறிப்பிடுகிறார்:

அளவுரு (Parameter)மதிப்பு
amountADesired1000
amountBDesired1000
amountAMin900
amountBMin800

பரிமாற்ற விகிதம் 0.9 மற்றும் 1.25-க்கு இடையில் இருக்கும் வரை, பரிவர்த்தனை நடைபெறும். பரிமாற்ற விகிதம் அந்த வரம்பிற்கு வெளியே சென்றால், பரிவர்த்தனை ரத்து செய்யப்படும்.

இந்த முன்னெச்சரிக்கைக்குக் காரணம், பரிவர்த்தனைகள் உடனடியாக நடைபெறுவதில்லை, நீங்கள் அவற்றைச் சமர்ப்பிக்கிறீர்கள், இறுதியில் ஒரு வேலிடேட்டர் (validator) அவற்றை ஒரு பிளாக்கில் சேர்க்கப் போகிறார் (உங்கள் எரிவாயு விலை (gas price) மிகக் குறைவாக இல்லாவிட்டால், அதை மேலெழுத அதே நான்ஸ் (nonce) மற்றும் அதிக எரிவாயு விலையுடன் மற்றொரு பரிவர்த்தனையை நீங்கள் சமர்ப்பிக்க வேண்டும்). சமர்ப்பிப்பதற்கும் சேர்ப்பதற்கும் இடையிலான இடைவெளியில் என்ன நடக்கிறது என்பதை உங்களால் கட்டுப்படுத்த முடியாது.

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

இருப்புக்களுக்கு இடையிலான தற்போதைய விகிதத்திற்கு சமமான விகிதத்தைக் கொண்டிருக்க பணப்புழக்கத்தை வழங்குபவர் டெபாசிட் செய்ய வேண்டிய தொகைகளை இந்தச் செயல்பாடு வழங்குகிறது.

1 // ஜோடி இன்னும் இல்லை என்றால் அதை உருவாக்கவும்
2 if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
3 IUniswapV2Factory(factory).createPair(tokenA, tokenB);
4 }

இந்த டோக்கன் ஜோடிக்கு இன்னும் பரிமாற்றம் இல்லை என்றால், அதை உருவாக்கவும்.

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

ஜோடியில் உள்ள தற்போதைய இருப்புக்களைப் பெறவும்.

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

தற்போதைய இருப்புக்கள் காலியாக இருந்தால், இது ஒரு புதிய ஜோடி பரிமாற்றம். டெபாசிட் செய்ய வேண்டிய தொகைகள் பணப்புழக்கத்தை வழங்குபவர் வழங்க விரும்பும் தொகைகளுக்குச் சரியாகச் சமமாக இருக்க வேண்டும்.

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

தொகைகள் என்னவாக இருக்கும் என்பதை நாம் பார்க்க வேண்டும் என்றால், இந்தச் செயல்பாட்டைப் (opens in a new tab) பயன்படுத்தி உகந்த தொகையைப் பெறுகிறோம். தற்போதைய இருப்புக்களின் அதே விகிதத்தை நாங்கள் விரும்புகிறோம்.

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

amountBOptimal பணப்புழக்கத்தை வழங்குபவர் டெபாசிட் செய்ய விரும்பும் தொகையை விடச் சிறியதாக இருந்தால், பணப்புழக்கத்தை டெபாசிட் செய்பவர் நினைப்பதை விட டோக்கன் B தற்போது அதிக மதிப்புமிக்கது என்று அர்த்தம், எனவே சிறிய தொகை தேவைப்படுகிறது.

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

உகந்த B தொகை விரும்பிய B தொகையை விட அதிகமாக இருந்தால், பணப்புழக்கத்தை டெபாசிட் செய்பவர் நினைப்பதை விட B டோக்கன்கள் தற்போது குறைவான மதிப்புடையவை என்று அர்த்தம், எனவே அதிக தொகை தேவைப்படுகிறது. இருப்பினும், விரும்பிய தொகை அதிகபட்சமாகும், எனவே எங்களால் அதைச் செய்ய முடியாது. அதற்குப் பதிலாக, விரும்பிய B டோக்கன்களின் தொகைக்கு உகந்த A டோக்கன்களின் எண்ணிக்கையைக் கணக்கிடுகிறோம்.

இவை அனைத்தையும் ஒன்றாக இணைத்தால் இந்த வரைபடம் கிடைக்கும். நீங்கள் ஆயிரம் A டோக்கன்களையும் (நீலக் கோடு) ஆயிரம் B டோக்கன்களையும் (சிவப்புக் கோடு) டெபாசிட் செய்ய முயற்சிக்கிறீர்கள் என்று வைத்துக்கொள்வோம். x அச்சு என்பது பரிமாற்ற விகிதம், A/B. x=1 ஆக இருந்தால், அவை சம மதிப்புடையவை மற்றும் நீங்கள் ஒவ்வொன்றிலும் ஆயிரத்தை டெபாசிட் செய்கிறீர்கள். x=2 ஆக இருந்தால், A என்பது B-இன் மதிப்பை விட இருமடங்காகும் (ஒவ்வொரு A டோக்கனுக்கும் இரண்டு B டோக்கன்களைப் பெறுவீர்கள்) எனவே நீங்கள் ஆயிரம் B டோக்கன்களை டெபாசிட் செய்கிறீர்கள், ஆனால் 500 A டோக்கன்களை மட்டுமே டெபாசிட் செய்கிறீர்கள். x=0.5 ஆக இருந்தால், நிலைமை தலைகீழாக மாறும், ஆயிரம் A டோக்கன்கள் மற்றும் ஐநூறு B டோக்கன்கள்.

வரைபடம்

நீங்கள் பணப்புழக்கத்தை நேரடியாக கோர் ஒப்பந்தத்தில் டெபாசிட் செய்யலாம் (UniswapV2Pair::mint (opens in a new tab)-ஐப் பயன்படுத்தி), ஆனால் கோர் ஒப்பந்தம் அது ஏமாற்றப்படவில்லையா என்பதை மட்டுமே சரிபார்க்கிறது, எனவே உங்கள் பரிவர்த்தனையை நீங்கள் சமர்ப்பிக்கும் நேரத்திற்கும் அது செயல்படுத்தப்படும் நேரத்திற்கும் இடையில் பரிமாற்ற விகிதம் மாறினால் மதிப்பை இழக்கும் அபாயம் உள்ளது. நீங்கள் பெரிஃபெரி ஒப்பந்தத்தைப் பயன்படுத்தினால், நீங்கள் டெபாசிட் செய்ய வேண்டிய தொகையை அது கணக்கிட்டு உடனடியாக டெபாசிட் செய்கிறது, எனவே பரிமாற்ற விகிதம் மாறாது மற்றும் நீங்கள் எதையும் இழக்க மாட்டீர்கள்.

1 function addLiquidity(
2 address tokenA,
3 address tokenB,
4 uint amountADesired,
5 uint amountBDesired,
6 uint amountAMin,
7 uint amountBMin,
8 address to,
9 uint deadline

பணப்புழக்கத்தை டெபாசிட் செய்ய ஒரு பரிவர்த்தனையால் இந்தச் செயல்பாடு அழைக்கப்படலாம். பெரும்பாலான அளவுருக்கள் மேலே உள்ள _addLiquidity-இல் உள்ளதைப் போலவே இருக்கும், இரண்டு விதிவிலக்குகளுடன்:

. to என்பது பணப்புழக்கக் குழுவில் பணப்புழக்கத்தை வழங்குபவரின் பங்கைக் காட்ட புதிய பணப்புழக்க டோக்கன்களைப் பெறும் முகவரியாகும் . deadline என்பது பரிவர்த்தனைக்கான நேர வரம்பாகும்

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

உண்மையில் டெபாசிட் செய்ய வேண்டிய தொகைகளை நாங்கள் கணக்கிடுகிறோம், பின்னர் பணப்புழக்கக் குழுவின் முகவரியைக் கண்டறிகிறோம். எரிவாயுவைச் சேமிக்க, ஃபேக்டரியைக் கேட்பதன் மூலம் இதைச் செய்ய மாட்டோம், ஆனால் pairFor என்ற லைப்ரரி செயல்பாட்டைப் பயன்படுத்துகிறோம் (கீழே உள்ள லைப்ரரிகளில் பார்க்கவும்)

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

பயனரிடமிருந்து சரியான அளவு டோக்கன்களை ஜோடி பரிமாற்றத்திற்கு மாற்றவும்.

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

பதிலுக்கு, குழுவின் பகுதி உரிமைக்காக to முகவரிக்கு பணப்புழக்க டோக்கன்களை வழங்கவும். கோர் ஒப்பந்தத்தின் mint செயல்பாடு தன்னிடம் எத்தனை கூடுதல் டோக்கன்கள் உள்ளன என்பதைப் பார்க்கிறது (கடைசியாக பணப்புழக்கம் மாறியபோது இருந்ததை ஒப்பிடுகையில்) அதற்கேற்ப பணப்புழக்கத்தை உருவாக்குகிறது.

1 function addLiquidityETH(
2 address token,
3 uint amountTokenDesired,

ஒரு பணப்புழக்கத்தை வழங்குபவர் டோக்கன்/ETH ஜோடி பரிமாற்றத்திற்கு பணப்புழக்கத்தை வழங்க விரும்பும்போது, சில வேறுபாடுகள் உள்ளன. பணப்புழக்கத்தை வழங்குபவருக்காக ETH-ஐ ரேப் (wrap) செய்வதை ஒப்பந்தம் கையாளுகிறது. பயனர் எத்தனை ETH-ஐ டெபாசிட் செய்ய விரும்புகிறார் என்பதைக் குறிப்பிடத் தேவையில்லை, ஏனெனில் பயனர் அவற்றை பரிவர்த்தனையுடன் அனுப்புகிறார் (தொகை msg.value-இல் கிடைக்கும்).

1 uint amountTokenMin,
2 uint amountETHMin,
3 address to,
4 uint deadline
5 ) 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 amountETHMin
13 );
14 address pair = UniswapV2Library.pairFor(factory, token, WETH);
15 TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
16 IWETH(WETH).deposit{value: amountETH}();
17 assert(IWETH(WETH).transfer(pair, amountETH));

ETH-ஐ டெபாசிட் செய்ய, ஒப்பந்தம் முதலில் அதை WETH ஆக ரேப் செய்கிறது, பின்னர் WETH-ஐ ஜோடிக்கு மாற்றுகிறது. பரிமாற்றம் ஒரு assert-இல் ரேப் செய்யப்பட்டுள்ளது என்பதைக் கவனியுங்கள். இதன் பொருள் பரிமாற்றம் தோல்வியுற்றால் இந்த ஒப்பந்த அழைப்பும் தோல்வியடையும், எனவே ரேப்பிங் உண்மையில் நடக்காது.

1 liquidity = IUniswapV2Pair(pair).mint(to);
2 // மீதமுள்ள (dust) eth ஏதேனும் இருந்தால், அதைத் திருப்பித் தரவும்
3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
4 }

பயனர் ஏற்கனவே எங்களுக்கு ETH-ஐ அனுப்பியுள்ளார், எனவே ஏதேனும் கூடுதல் மீதம் இருந்தால் (பயனர் நினைத்ததை விட மற்ற டோக்கன் குறைவான மதிப்புடையது என்பதால்), நாங்கள் பணத்தைத் திரும்பப் பெற (refund) வேண்டும்.

பணப்புழக்கத்தை நீக்குதல்

இந்தச் செயல்பாடுகள் பணப்புழக்கத்தை அகற்றி, பணப்புழக்கத்தை வழங்குபவருக்குத் திருப்பிச் செலுத்தும்.

1 // **** பணப்புழக்கத்தை நீக்கவும் ****
2 function removeLiquidity(
3 address tokenA,
4 address tokenB,
5 uint liquidity,
6 uint amountAMin,
7 uint amountBMin,
8 address to,
9 uint deadline
10 ) 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 }

முதலில் பரிமாற்றத்தைச் செய்துவிட்டு, அது சட்டபூர்வமானதா என்பதைச் சரிபார்ப்பது பரவாயில்லை, ஏனென்றால் அது இல்லையென்றால், அனைத்து நிலை மாற்றங்களிலிருந்தும் (state changes) நாங்கள் வெளியேறுவோம்.

1 function removeLiquidityETH(
2 address token,
3 uint liquidity,
4 uint amountTokenMin,
5 uint amountETHMin,
6 address to,
7 uint deadline
8 ) 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 deadline
17 );
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 s
10 ) 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 s
26 ) 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 }

இந்தச் செயல்பாடுகள் ஈதர் இல்லாத பயனர்களை அனுமதி பொறிமுறையைப் (permit mechanism) பயன்படுத்தி குழுவிலிருந்து திரும்பப் பெற அனுமதிக்க மெட்டா-பரிவர்த்தனைகளை (meta-transactions) ரிலே செய்கின்றன.

1
2 // **** பணப்புழக்கத்தை நீக்கவும் (பரிமாற்றக் கட்டண டோக்கன்களை ஆதரிக்கிறது) ****
3 function removeLiquidityETHSupportingFeeOnTransferTokens(
4 address token,
5 uint liquidity,
6 uint amountTokenMin,
7 uint amountETHMin,
8 address to,
9 uint deadline
10 ) 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 deadline
19 );
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 s
11 ) 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, deadline
17 );
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). அதற்குப் பதிலாக, ஸ்வாப் (swap) செயல்பாடுகள் ஒரு பாதையின் (path) கருத்தை ஆதரிக்கின்றன. ஒரு வர்த்தகர் A-ஐ B-க்கும், B-ஐ C-க்கும், C-ஐ D-க்கும் பரிமாறிக்கொள்ளலாம், எனவே நேரடி A-D ஜோடி பரிமாற்றம் தேவையில்லை.

இந்தச் சந்தைகளில் உள்ள விலைகள் ஒத்திசைக்கப்படும் (synchronized) முனைகின்றன, ஏனெனில் அவை ஒத்திசைவில் இல்லாதபோது அது ஆர்பிட்ரேஜுக்கான (arbitrage) வாய்ப்பை உருவாக்குகிறது. எடுத்துக்காட்டாக, A, B மற்றும் C ஆகிய மூன்று டோக்கன்களைக் கற்பனை செய்து பாருங்கள். ஒவ்வொரு ஜோடிக்கும் ஒன்று என மூன்று ஜோடி பரிமாற்றங்கள் உள்ளன.

  1. ஆரம்ப நிலைமை
  2. ஒரு வர்த்தகர் 24.695 A டோக்கன்களை விற்று 25.305 B டோக்கன்களைப் பெறுகிறார்.
  3. வர்த்தகர் 24.695 B டோக்கன்களை 25.305 C டோக்கன்களுக்கு விற்கிறார், தோராயமாக 0.61 B டோக்கன்களை லாபமாக வைத்திருக்கிறார்.
  4. பின்னர் வர்த்தகர் 24.695 C டோக்கன்களை 25.305 A டோக்கன்களுக்கு விற்கிறார், தோராயமாக 0.61 C டோக்கன்களை லாபமாக வைத்திருக்கிறார். வர்த்தகரிடம் 0.61 கூடுதல் A டோக்கன்களும் உள்ளன (வர்த்தகர் முடிவடையும் 25.305, அசல் முதலீடான 24.695-ஐக் கழித்தால்).
படிA-B பரிமாற்றம்B-C பரிமாற்றம்A-C பரிமாற்றம்
1A:1000 B:1050 A/B=1.05B:1000 C:1050 B/C=1.05A:1050 C:1000 C/A=1.05
2A:1024.695 B:1024.695 A/B=1B:1000 C:1050 B/C=1.05A:1050 C:1000 C/A=1.05
3A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1050 C:1000 C/A=1.05
4A:1024.695 B:1024.695 A/B=1B:1024.695 C:1024.695 B/C=1A:1024.695 C:1024.695 C/A=1
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 }

டோக்கன்களை மாற்றுவதற்கு (swap) ஜோடி பரிமாற்றத்தை உண்மையில் அழைக்கவும். பரிமாற்றத்தைப் பற்றிச் சொல்ல எங்களுக்கு ஒரு கால்பேக் (callback) தேவையில்லை, எனவே அந்தப் புலத்தில் எந்த பைட்டுகளையும் நாங்கள் அனுப்ப மாட்டோம்.

1 function swapExactTokensForTokens(

ஒரு டோக்கனை மற்றொன்றுக்கு மாற்றுவதற்கு வர்த்தகர்களால் இந்தச் செயல்பாடு நேரடியாகப் பயன்படுத்தப்படுகிறது.

1 uint amountIn,
2 uint amountOutMin,
3 address[] calldata path,

இந்த அளவுரு ERC-20 ஒப்பந்தங்களின் முகவரிகளைக் கொண்டுள்ளது. மேலே விளக்கியபடி, இது ஒரு வரிசையாகும் (array), ஏனெனில் உங்களிடம் உள்ள சொத்திலிருந்து நீங்கள் விரும்பும் சொத்தைப் பெற நீங்கள் பல ஜோடி பரிமாற்றங்கள் வழியாகச் செல்ல வேண்டியிருக்கும்.

Solidity-இல் ஒரு செயல்பாட்டு அளவுருவை memory அல்லது calldata-இல் சேமிக்க முடியும். செயல்பாடு ஒப்பந்தத்திற்கான நுழைவுப் புள்ளியாக இருந்தால், பயனரிடமிருந்து நேரடியாக (பரிவர்த்தனையைப் பயன்படுத்தி) அல்லது வேறு ஒப்பந்தத்திலிருந்து அழைக்கப்பட்டால், அளவுருவின் மதிப்பை நேரடியாக கால் டேட்டாவிலிருந்து (call data) எடுக்கலாம். மேலே உள்ள _swap போல, செயல்பாடு உள்நாட்டில் அழைக்கப்பட்டால், அளவுருக்கள் memory-இல் சேமிக்கப்பட வேண்டும். அழைக்கப்பட்ட ஒப்பந்தத்தின் கண்ணோட்டத்தில் calldata படிக்க மட்டுமே (read only) முடியும்.

uint அல்லது address போன்ற ஸ்கேலார் (scalar) வகைகளுடன் கம்பைலர் (compiler) சேமிப்பகத்தின் தேர்வை நமக்காகக் கையாளுகிறது, ஆனால் நீளமான மற்றும் அதிக விலையுயர்ந்த வரிசைகளுடன் (arrays), பயன்படுத்த வேண்டிய சேமிப்பகத்தின் வகையை நாங்கள் குறிப்பிடுகிறோம்.

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

திரும்பப் பெறும் மதிப்புகள் (Return values) எப்போதும் நினைவகத்தில் (memory) வழங்கப்படும்.

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 deadline
7 ) 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, ஒரு வர்த்தகர் தான் கொடுக்கத் தயாராக இருக்கும் உள்ளீட்டு டோக்கன்களின் சரியான எண்ணிக்கையையும், பதிலுக்கு அவர் பெறத் தயாராக இருக்கும் வெளியீட்டு டோக்கன்களின் குறைந்தபட்ச எண்ணிக்கையையும் குறிப்பிட அனுமதிக்கிறது. இந்தச் செயல்பாடு தலைகீழ் ஸ்வாப்பைச் செய்கிறது, இது ஒரு வர்த்தகர் தான் விரும்பும் வெளியீட்டு டோக்கன்களின் எண்ணிக்கையையும், அவற்றுக்காக அவர் செலுத்தத் தயாராக இருக்கும் உள்ளீட்டு டோக்கன்களின் அதிகபட்ச எண்ணிக்கையையும் குறிப்பிட அனுமதிக்கிறது.

இரண்டு சந்தர்ப்பங்களிலும், வர்த்தகர் முதலில் இந்தப் பெரிஃபெரி ஒப்பந்தத்திற்கு அவற்றை மாற்றுவதற்கு அனுமதி (allowance) அளிக்க வேண்டும்.

1 function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
2 external
3 virtual
4 override
5 payable
6 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 external
20 virtual
21 override
22 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 external
40 virtual
41 override
42 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 external
59 virtual
60 override
61 payable
62 ensure(deadline)
63 returns (uint[] memory amounts)
64 {
65 require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
66 amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
67 require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
68 IWETH(WETH).deposit{value: amounts[0]}();
69 assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
70 _swap(amounts, path, to);
71 // மீதமுள்ள (dust) eth ஏதேனும் இருந்தால், அதைத் திருப்பித் தரவும்
72 if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
73 }

இந்த நான்கு வகைகளும் ETH மற்றும் டோக்கன்களுக்கு இடையிலான வர்த்தகத்தை உள்ளடக்கியது. ஒரே வித்தியாசம் என்னவென்றால், நாங்கள் வர்த்தகரிடமிருந்து ETH-ஐப் பெற்று WETH-ஐ உருவாக்கப் பயன்படுத்துகிறோம், அல்லது பாதையில் உள்ள கடைசி பரிமாற்றத்திலிருந்து WETH-ஐப் பெற்று அதை எரித்து, வர்த்தகருக்கு ETH-ஐத் திருப்பி அனுப்புகிறோம்.

1 // **** ஸ்வாப் (பரிமாற்றக் கட்டண டோக்கன்களை ஆதரிக்கிறது) ****
2 // ஆரம்பத் தொகை ஏற்கனவே முதல் ஜோடிக்கு அனுப்பப்பட்டிருக்க வேண்டும்
3 function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {

பரிமாற்ற அல்லது சேமிப்பக கட்டணங்களைக் கொண்ட டோக்கன்களை மாற்றுவதற்கான உள் செயல்பாடு இதுவாகும் (இந்தச் சிக்கலைத் (opens in a new tab) தீர்க்க).

1 for (uint i; i < path.length - 1; i++) {
2 (address input, address output) = (path[i], path[i + 1]);
3 (address token0,) = UniswapV2Library.sortTokens(input, output);
4 IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
5 uint amountInput;
6 uint amountOutput;
7 { // 'stack too deep' பிழைகளைத் தவிர்ப்பதற்கான ஸ்கோப்
8 (uint reserve0, uint reserve1,) = pair.getReserves();
9 (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
10 amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
11 amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);

பரிமாற்றக் கட்டணங்கள் காரணமாக, ஒவ்வொரு பரிமாற்றத்திலிருந்தும் எவ்வளவு பெறுகிறோம் என்பதைச் சொல்ல getAmountsOut செயல்பாட்டை நாம் நம்ப முடியாது (அசல் _swap-ஐ அழைப்பதற்கு முன் நாம் செய்யும் விதம்). அதற்குப் பதிலாக நாம் முதலில் பரிமாற்றம் செய்ய வேண்டும், பின்னர் எத்தனை டோக்கன்களைத் திரும்பப் பெற்றோம் என்பதைப் பார்க்க வேண்டும்.

குறிப்பு: கோட்பாட்டில் நாம் _swap-க்கு பதிலாக இந்தச் செயல்பாட்டைப் பயன்படுத்தலாம், ஆனால் சில சந்தர்ப்பங்களில் (எடுத்துக்காட்டாக, தேவையான குறைந்தபட்சத்தைப் பூர்த்தி செய்ய முடிவில் போதுமான அளவு இல்லாததால் பரிமாற்றம் மாற்றியமைக்கப்பட்டால்) அது அதிக எரிவாயு செலவாகும். பரிமாற்றக் கட்டண டோக்கன்கள் மிகவும் அரிதானவை, எனவே நாம் அவற்றுக்கு இடமளிக்க வேண்டியிருக்கும் போது, அனைத்து ஸ்வாப்களும் அவற்றில் ஒன்றின் வழியாகவாவது செல்கின்றன என்று கருத வேண்டிய அவசியமில்லை.

1 }
2 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
3 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
4 pair.swap(amount0Out, amount1Out, to, new bytes(0));
5 }
6 }
7
8
9 function swapExactTokensForTokensSupportingFeeOnTransferTokens(
10 uint amountIn,
11 uint amountOutMin,
12 address[] calldata path,
13 address to,
14 uint deadline
15 ) external virtual override ensure(deadline) {
16 TransferHelper.safeTransferFrom(
17 path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
18 );
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 deadline
33 )
34 external
35 virtual
36 override
37 payable
38 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 deadline
59 )
60 external
61 virtual
62 override
63 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]), amountIn
68 );
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 public
8 pure
9 virtual
10 override
11 returns (uint amountOut)
12 {
13 return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
14 }
15
16 function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
17 public
18 pure
19 virtual
20 override
21 returns (uint amountIn)
22 {
23 return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
24 }
25
26 function getAmountsOut(uint amountIn, address[] memory path)
27 public
28 view
29 virtual
30 override
31 returns (uint[] memory amounts)
32 {
33 return UniswapV2Library.getAmountsOut(factory, amountIn, path);
34 }
35
36 function getAmountsIn(uint amountOut, address[] memory path)
37 public
38 view
39 virtual
40 override
41 returns (uint[] memory amounts)
42 {
43 return UniswapV2Library.getAmountsIn(factory, amountOut, path);
44 }
45}

இந்தச் செயல்பாடுகள் UniswapV2Library செயல்பாடுகளை அழைக்கும் ப்ராக்ஸிகள் (proxies) மட்டுமே.

UniswapV2Migrator.sol

பழைய v1-லிருந்து v2-க்கு பரிமாற்றங்களை மாற்ற இந்த ஒப்பந்தம் பயன்படுத்தப்பட்டது. இப்போது அவை மாற்றப்பட்டுவிட்டதால், இது இனி பொருத்தமானதல்ல.

நூலகங்கள்

SafeMath library (opens in a new tab) நன்கு ஆவணப்படுத்தப்பட்டுள்ளது, எனவே அதை இங்கே ஆவணப்படுத்த வேண்டிய அவசியமில்லை.

Math

இந்த நூலகத்தில் Solidity குறியீட்டில் பொதுவாகத் தேவைப்படாத சில கணிதச் செயல்பாடுகள் உள்ளன, எனவே அவை மொழியின் ஒரு பகுதியாக இல்லை.

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;

வர்க்கமூலத்தை விட (square root) அதிகமாக இருக்கும் ஒரு மதிப்பீடாக 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}

நிலையான புள்ளி பின்னங்கள் (Fixed Point Fractions - UQ112x112)

இந்த நூலகம் பின்னங்களைக் கையாளுகிறது, இவை பொதுவாக Ethereum எண்கணிதத்தின் ஒரு பகுதியாக இருக்காது. இது x என்ற எண்ணை x*2^112 என குறியாக்கம் செய்வதன் மூலம் இதைச் செய்கிறது. இது அசல் கூட்டல் மற்றும் கழித்தல் ஆப்கோடுகளை (opcodes) எந்த மாற்றமும் இல்லாமல் பயன்படுத்த அனுமதிக்கிறது.

1pragma solidity =0.5.16;
2
3// பைனரி நிலையான புள்ளி எண்களைக் கையாளுவதற்கான ஒரு லைப்ரரி (https://wikipedia.org/wiki/Q_(number_format))
4
5// வரம்பு: [0, 2**112 - 1]
6// தெளிவுத்திறன்: 1 / 2**112
7
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 ஆல் பெருக்கப்படாது. எனவே அதற்குப் பதிலாகப் பகுதிக்காக (denominator) ஒரு முழு எண்ணை எடுத்துக்கொள்கிறோம். பெருக்கலைச் செய்வதற்கும் இதேபோன்ற ஒரு தந்திரத்தைப் பயன்படுத்த வேண்டியிருந்திருக்கும், ஆனால் UQ112x112 மதிப்புகளின் பெருக்கலை நாம் செய்ய வேண்டியதில்லை.

UniswapV2Library

இந்த நூலகம் periphery ஒப்பந்தங்களால் மட்டுமே பயன்படுத்தப்படுகிறது

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 }

இரண்டு டோக்கன்களையும் முகவரியின் அடிப்படையில் வரிசைப்படுத்தவும், இதனால் அவற்றுக்கான ஜோடி பரிமாற்றத்தின் (pair exchange) முகவரியை நாம் பெற முடியும். இது அவசியமானது, ஏனென்றால் இல்லையெனில் நமக்கு இரண்டு சாத்தியக்கூறுகள் இருக்கும், ஒன்று A,B அளவுருக்களுக்கும் மற்றொன்று B,A அளவுருக்களுக்கும், இது ஒன்றிற்குப் பதிலாக இரண்டு பரிமாற்றங்களுக்கு வழிவகுக்கும்.

1 // எந்தவொரு வெளிப்புற அழைப்புகளையும் செய்யாமல் ஒரு ஜோடிக்கான CREATE2 முகவரியைக் கணக்கிடுகிறது
2 function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
3 (address token0, address token1) = sortTokens(tokenA, tokenB);
4 pair = address(uint(keccak256(abi.encodePacked(
5 hex'ff',
6 factory,
7 keccak256(abi.encodePacked(token0, token1)),
8 hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code ஹாஷ்
9 ))));
10 }

இந்தச் செயல்பாடு இரண்டு டோக்கன்களுக்கான ஜோடி பரிமாற்றத்தின் முகவரியைக் கணக்கிடுகிறது. இந்த ஒப்பந்தம் CREATE2 opcode (opens in a new tab) ஐப் பயன்படுத்தி உருவாக்கப்பட்டுள்ளது, எனவே அது பயன்படுத்தும் அளவுருக்கள் நமக்குத் தெரிந்தால் அதே அல்காரிதத்தைப் பயன்படுத்தி முகவரியைக் கணக்கிடலாம். இது factory-யிடம் கேட்பதை விட மிகவும் மலிவானது, மேலும்

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 }

இந்தச் செயல்பாடு ஜோடி பரிமாற்றம் கொண்டுள்ள இரண்டு டோக்கன்களின் இருப்புகளை (reserves) வழங்குகிறது. இது டோக்கன்களை எந்த வரிசையிலும் பெறலாம் என்பதையும், உள் பயன்பாட்டிற்காக அவற்றை வரிசைப்படுத்துகிறது என்பதையும் நினைவில் கொள்ளவும்.

1 // ஒரு சொத்தின் குறிப்பிட்ட அளவு மற்றும் ஜோடியின் இருப்புகள் கொடுக்கப்பட்டால், மற்ற சொத்தின் சமமான அளவை வழங்கும்
2 function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
3 require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
4 require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
5 amountB = amountA.mul(reserveB) / reserveA;
6 }

எந்தக் கட்டணமும் இல்லை என்றால், டோக்கன் A-க்கு ஈடாக நீங்கள் பெறும் டோக்கன் B-இன் அளவை இந்தச் செயல்பாடு வழங்குகிறது. பரிமாற்றம் மாற்று விகிதத்தை (exchange rate) மாற்றுகிறது என்பதை இந்தக் கணக்கீடு கணக்கில் எடுத்துக்கொள்கிறது.

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 }

Solidity இயல்பாகவே பின்னங்களைக் கையாளுவதில்லை, எனவே நாம் தொகையை 0.997 ஆல் பெருக்க முடியாது. அதற்குப் பதிலாக, தொகுதியை (numerator) 997 ஆலும், பகுதியை (denominator) 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}

பல ஜோடி பரிமாற்றங்கள் வழியாகச் செல்ல வேண்டியிருக்கும் போது மதிப்புகளை அடையாளம் காண்பதை இந்த இரண்டு செயல்பாடுகளும் கையாளுகின்றன.

Transfer Helper

இந்த நூலகம் (opens in a new tab) ERC-20 மற்றும் Ethereum பரிமாற்றங்களைச் சுற்றி வெற்றிக்கான சரிபார்ப்புகளைச் சேர்க்கிறது, இதன் மூலம் revert மற்றும் false மதிப்பு திரும்புதல் ஆகிய இரண்டையும் ஒரே மாதிரியாகக் கையாளுகிறது.

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3pragma solidity >=0.6.0;
4
5// ERC20 டோக்கன்களுடன் தொடர்புகொள்வதற்கும், தொடர்ந்து true/false வழங்காத ETH-ஐ அனுப்புவதற்குமான உதவி முறைகள்
6library TransferHelper {
7 function safeApprove(
8 address token,
9 address to,
10 uint256 value
11 ) internal {
12 // bytes4(keccak256(bytes('approve(address,uint256)')));
13 (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
14

நாம் வேறு ஒரு ஒப்பந்தத்தை இரண்டு வழிகளில் ஒன்றில் அழைக்கலாம்:

  • ஒரு செயல்பாட்டு அழைப்பை (function call) உருவாக்க இடைமுக வரையறையைப் (interface definition) பயன்படுத்துதல்
  • அழைப்பை உருவாக்க பயன்பாட்டு பைனரி இடைமுகத்தை (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 தரநிலைக்கு முன்பு உருவாக்கப்பட்ட டோக்கன்களுடன் பின்னோக்கிய இணக்கத்தன்மைக்காக (backwards compatibility), ஒரு ERC-20 அழைப்பு revert ஆவதன் மூலம் தோல்வியடையலாம் (இந்த நிலையில் success என்பது false ஆக இருக்கும்) அல்லது வெற்றிகரமாகச் செயல்பட்டு false மதிப்பை வழங்கலாம் (இந்த நிலையில் வெளியீட்டுத் தரவு இருக்கும், அதை நீங்கள் பூலியனாக டீகோட் செய்தால் false கிடைக்கும்).

1
2
3 function safeTransfer(
4 address token,
5 address to,
6 uint256 value
7 ) 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 இன் transfer செயல்பாட்டை (opens in a new tab) செயல்படுத்துகிறது, இது ஒரு கணக்கு வேறு ஒரு கணக்கால் வழங்கப்பட்ட அனுமதியைச் (allowance) செலவிட அனுமதிக்கிறது.

1
2 function safeTransferFrom(
3 address token,
4 address from,
5 address to,
6 uint256 value
7 ) 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) செயல்படுத்துகிறது, இது ஒரு கணக்கு வேறு ஒரு கணக்கால் வழங்கப்பட்ட அனுமதியைச் (allowance) செலவிட அனுமதிக்கிறது.

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}

இந்தச் செயல்பாடு ஒரு கணக்கிற்கு ஈதரை (ether) மாற்றுகிறது. வேறு ஒரு ஒப்பந்தத்திற்கான எந்தவொரு அழைப்பும் ஈதரை அனுப்ப முயற்சிக்கலாம். நாம் உண்மையில் எந்தச் செயல்பாட்டையும் அழைக்கத் தேவையில்லை என்பதால், அழைப்புடன் எந்தத் தரவையும் அனுப்ப மாட்டோம்.

முடிவுரை

இது சுமார் 50 பக்கங்களைக் கொண்ட ஒரு நீண்ட கட்டுரையாகும். நீங்கள் இதுவரை வந்திருந்தால், வாழ்த்துகள்! (சிறிய மாதிரி நிரல்களுக்கு மாறாக) ஒரு நிஜ-வாழ்க்கை பயன்பாட்டை எழுதுவதில் உள்ள பரிசீலனைகளை நீங்கள் இப்போது புரிந்துகொண்டிருப்பீர்கள் என்றும், உங்கள் சொந்த பயன்பாட்டுத் தேவைகளுக்கான ஒப்பந்தங்களை எழுதுவதற்கு நீங்கள் இப்போது சிறப்பாகத் தயாராக இருப்பீர்கள் என்றும் நம்புகிறோம்.

இப்போது சென்று பயனுள்ள ஒன்றை எழுதி எங்களை ஆச்சரியப்படுத்துங்கள்.

எனது மேலும் பல பணிகளை இங்கே காணுங்கள் (opens in a new tab).

பக்கம் கடைசியாகப் புதுப்பிக்கப்பட்டது: 3 மார்ச், 2026

இந்த வழிகாட்டி பயனுள்ளதாக இருந்ததா?