مرکزی مواد پر جائیں

Uniswap-v2 کنٹریکٹ کا جائزہ

Solidity
dapps
درمیانی
اوری پومرانٹز
1 مئی، 2021
72 منٹ کی پڑھائی

تعارف

Uniswap v2 (opens in a new tab) کسی بھی دو ERC-20 ٹوکنز کے درمیان ایکسچینج مارکیٹ بنا سکتا ہے۔ اس مضمون میں ہم ان کنٹریکٹس کے سورس کوڈ کا جائزہ لیں گے جو اس پروٹوکول کو نافذ کرتے ہیں اور دیکھیں گے کہ انہیں اس طرح کیوں لکھا گیا ہے۔

Uniswap کیا کرتا ہے؟

بنیادی طور پر، صارفین کی دو اقسام ہیں: لیکویڈیٹی فراہم کرنے والے (liquidity providers) اور ٹریڈرز۔

لیکویڈیٹی فراہم کرنے والے پول کو دو ٹوکن فراہم کرتے ہیں جن کا تبادلہ کیا جا سکتا ہے (ہم انہیں Token0 اور Token1 کہیں گے)۔ اس کے بدلے میں، انہیں ایک تیسرا ٹوکن ملتا ہے جو پول کی جزوی ملکیت کی نمائندگی کرتا ہے جسے لیکویڈیٹی ٹوکن کہا جاتا ہے۔

ٹریڈرز پول میں ایک قسم کا ٹوکن بھیجتے ہیں اور لیکویڈیٹی فراہم کرنے والوں کے فراہم کردہ پول سے دوسرا ٹوکن وصول کرتے ہیں (مثال کے طور پر، Token0 بھیجیں اور Token1 وصول کریں)۔ شرح مبادلہ (exchange rate) کا تعین پول میں موجود Token0 اور Token1 کی متعلقہ تعداد سے ہوتا ہے۔ اس کے علاوہ، پول لیکویڈیٹی پول کے انعام کے طور پر ایک چھوٹا سا فیصد لیتا ہے۔

جب لیکویڈیٹی فراہم کرنے والے اپنے اثاثے واپس لینا چاہتے ہیں تو وہ پول ٹوکنز کو برن (burn) کر سکتے ہیں اور اپنے ٹوکنز واپس حاصل کر سکتے ہیں، بشمول انعامات میں ان کا حصہ۔

مزید تفصیلی وضاحت کے لیے یہاں کلک کریں (opens in a new tab)۔

v2 کیوں؟ v3 کیوں نہیں؟

Uniswap v3 (opens in a new tab) ایک اپ گریڈ ہے جو v2 سے کہیں زیادہ پیچیدہ ہے۔ پہلے v2 سیکھنا اور پھر v3 کی طرف جانا زیادہ آسان ہے۔

کور کنٹریکٹس بمقابلہ پیریفیری کنٹریکٹس

Uniswap v2 کو دو حصوں میں تقسیم کیا گیا ہے، ایک کور (core) اور ایک پیریفیری (periphery)۔ یہ تقسیم کور کنٹریکٹس کو، جو اثاثے رکھتے ہیں اور اس لیے ان کا محفوظ ہونا لازمی ہے، زیادہ سادہ اور آڈٹ کرنے میں آسان بناتی ہے۔ ٹریڈرز کو درکار تمام اضافی فعالیت پھر پیریفیری کنٹریکٹس کے ذریعے فراہم کی جا سکتی ہے۔

ڈیٹا اور کنٹرول فلو

یہ ڈیٹا اور کنٹرول کا وہ فلو ہے جو اس وقت ہوتا ہے جب آپ Uniswap کے تین اہم ایکشنز انجام دیتے ہیں:

  1. مختلف ٹوکنز کے درمیان سویپ (Swap) کرنا
  2. مارکیٹ میں لیکویڈیٹی (liquidity) شامل کرنا اور انعام کے طور پر پیئر ایکسچینج ERC-20 لیکویڈیٹی ٹوکنز حاصل کرنا
  3. ERC-20 لیکویڈیٹی ٹوکنز کو برن (burn) کرنا اور وہ ERC-20 ٹوکنز واپس حاصل کرنا جنہیں پیئر ایکسچینج ٹریڈرز کو ایکسچینج کرنے کی اجازت دیتا ہے

سویپ (Swap)

یہ سب سے عام فلو ہے، جسے ٹریڈرز استعمال کرتے ہیں:

کالر (Caller)

  1. پیریفری (periphery) اکاؤنٹ کو اس رقم کا الاؤنس فراہم کریں جسے سویپ کیا جانا ہے۔
  2. پیریفری کنٹریکٹ کے بہت سے سویپ فنکشنز میں سے کسی ایک کو کال کریں (کون سا فنکشن کال کرنا ہے اس کا انحصار اس بات پر ہے کہ آیا ETH شامل ہے یا نہیں، آیا ٹریڈر جمع کرائے جانے والے ٹوکنز کی مقدار بتاتا ہے یا واپس ملنے والے ٹوکنز کی مقدار، وغیرہ)۔ ہر سویپ فنکشن ایک path قبول کرتا ہے، جو ان ایکسچینجز کی ایک ایرے (array) ہے جن سے گزرنا ہوتا ہے۔

پیریفری کنٹریکٹ میں (UniswapV2Router02.sol)

  1. ان رقوم کی نشاندہی کریں جن کی پاتھ (path) کے ساتھ ہر ایکسچینج پر ٹریڈ کرنے کی ضرورت ہے۔
  2. پاتھ پر ایٹریٹ (Iterate) کرتا ہے۔ راستے میں آنے والے ہر ایکسچینج کے لیے یہ ان پٹ ٹوکن بھیجتا ہے اور پھر ایکسچینج کے swap فنکشن کو کال کرتا ہے۔ زیادہ تر معاملات میں ٹوکنز کے لیے منزل کا ایڈریس پاتھ میں اگلا پیئر ایکسچینج ہوتا ہے۔ آخری ایکسچینج میں یہ ٹریڈر کی طرف سے فراہم کردہ ایڈریس ہوتا ہے۔

کور کنٹریکٹ میں (UniswapV2Pair.sol)

  1. تصدیق کریں کہ کور کنٹریکٹ کے ساتھ دھوکہ نہیں کیا جا رہا ہے اور وہ سویپ کے بعد کافی لیکویڈیٹی برقرار رکھ سکتا ہے۔
  2. دیکھیں کہ معلوم ریزرو (reserves) کے علاوہ ہمارے پاس کتنے اضافی ٹوکنز ہیں۔ یہ مقدار ان ان پٹ ٹوکنز کی تعداد ہے جو ہمیں ایکسچینج کرنے کے لیے موصول ہوئے ہیں۔
  3. آؤٹ پٹ ٹوکنز کو منزل پر بھیجیں۔
  4. ریزرو کی مقدار کو اپ ڈیٹ کرنے کے لیے _update کو کال کریں

واپس پیریفری کنٹریکٹ میں (UniswapV2Router02.sol)

  1. کوئی بھی ضروری کلین اپ (cleanup) انجام دیں (مثال کے طور پر، ٹریڈر کو بھیجنے کے لیے ETH واپس حاصل کرنے کی خاطر WETH ٹوکنز کو برن کریں)

لیکویڈیٹی شامل کریں

کالر (Caller)

  1. پیریفری اکاؤنٹ کو ان رقوم کا الاؤنس فراہم کریں جنہیں لیکویڈیٹی پول میں شامل کیا جانا ہے۔
  2. پیریفری کنٹریکٹ کے addLiquidity فنکشنز میں سے کسی ایک کو کال کریں۔

پیریفری کنٹریکٹ میں (UniswapV2Router02.sol)

  1. اگر ضروری ہو تو ایک نیا پیئر ایکسچینج بنائیں
  2. اگر پہلے سے کوئی پیئر ایکسچینج موجود ہے، تو شامل کیے جانے والے ٹوکنز کی مقدار کا حساب لگائیں۔ یہ دونوں ٹوکنز کے لیے یکساں ویلیو ہونی چاہیے، لہذا نئے ٹوکنز کا موجودہ ٹوکنز سے وہی تناسب ہونا چاہیے۔
  3. چیک کریں کہ آیا رقوم قابل قبول ہیں (کالرز ایک کم از کم مقدار بتا سکتے ہیں جس سے کم ہونے پر وہ لیکویڈیٹی شامل نہیں کرنا چاہیں گے)
  4. کور کنٹریکٹ کو کال کریں۔

کور کنٹریکٹ میں (UniswapV2Pair.sol)

  1. لیکویڈیٹی ٹوکنز منٹ (Mint) کریں اور انہیں کالر کو بھیجیں
  2. ریزرو کی مقدار کو اپ ڈیٹ کرنے کے لیے _update کو کال کریں

لیکویڈیٹی ہٹائیں

کالر (Caller)

  1. پیریفری اکاؤنٹ کو ان لیکویڈیٹی ٹوکنز کا الاؤنس فراہم کریں جنہیں انڈرلائنگ (underlying) ٹوکنز کے بدلے برن کیا جانا ہے۔
  2. پیریفری کنٹریکٹ کے removeLiquidity فنکشنز میں سے کسی ایک کو کال کریں۔

پیریفری کنٹریکٹ میں (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) اس اصل پول کو نافذ کرتا ہے جو ٹوکنز کا تبادلہ کرتا ہے۔ یہ 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';
سب دکھائیں

یہ وہ تمام انٹرفیسز ہیں جن کے بارے میں کانٹریکٹ کو جاننے کی ضرورت ہوتی ہے، یا تو اس لیے کہ کانٹریکٹ انہیں نافذ کرتا ہے (IUniswapV2Pair اور UniswapV2ERC20) یا اس لیے کہ یہ ان کانٹریکٹس کو کال کرتا ہے جو انہیں نافذ کرتے ہیں۔

1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {

یہ کانٹریکٹ UniswapV2ERC20 سے وراثت (inherits) میں ملتا ہے، جو لیکویڈیٹی ٹوکنز کے لیے 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 بٹس انٹیجر (integer) حصے کے لیے اور 112 بٹس کسر کے لیے ہیں۔ لہذا 1.0 کو 2^112 کے طور پر ظاہر کیا جاتا ہے، 1.5 کو 2^112 + 2^111 کے طور پر ظاہر کیا جاتا ہے، وغیرہ۔

اس لائبریری کے بارے میں مزید تفصیلات دستاویز میں آگے دستیاب ہیں۔

متغیرات (Variables)

1 uint public constant MINIMUM_LIQUIDITY = 10**3;

صفر سے تقسیم (division by zero) کے معاملات سے بچنے کے لیے، لیکویڈیٹی ٹوکنز کی ایک کم از کم تعداد ہمیشہ موجود رہتی ہے (لیکن ان کی ملکیت اکاؤنٹ صفر کے پاس ہوتی ہے)۔ وہ تعداد MINIMUM_LIQUIDITY ہے، یعنی ایک ہزار۔

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

یہ ERC-20 ٹرانسفر فنکشن کے لیے ABI سلیکٹر ہے۔ اس کا استعمال دو ٹوکن اکاؤنٹس میں ERC-20 ٹوکنز کو منتقل کرنے کے لیے کیا جاتا ہے۔

1 address public factory;

یہ وہ فیکٹری کانٹریکٹ ہے جس نے یہ پول بنایا ہے۔ ہر پول دو ERC-20 ٹوکنز کے درمیان ایک ایکسچینج ہوتا ہے، فیکٹری ایک مرکزی نقطہ ہے جو ان تمام پولز کو جوڑتا ہے۔

1 address public token0;
2 address public token1;

یہاں ان دو قسم کے ERC-20 ٹوکنز کے کانٹریکٹس کے ایڈریسز ہیں جن کا اس پول کے ذریعے تبادلہ کیا جا سکتا ہے۔

1 uint112 private reserve0; // ایک ہی سٹوریج سلاٹ استعمال کرتا ہے، getReserves کے ذریعے قابل رسائی ہے
2 uint112 private reserve1; // ایک ہی سٹوریج سلاٹ استعمال کرتا ہے، getReserves کے ذریعے قابل رسائی ہے

ہر ٹوکن کی قسم کے لیے پول کے پاس موجود ریزرو (reserves)۔ ہم فرض کرتے ہیں کہ دونوں ایک ہی قدر (value) کی نمائندگی کرتے ہیں، اور اس لیے ہر token0 کی قیمت reserve1/reserve0 token1 کے برابر ہے۔

1 uint32 private blockTimestampLast; // ایک ہی سٹوریج سلاٹ استعمال کرتا ہے، getReserves کے ذریعے قابل رسائی ہے

آخری بلاک کا ٹائم اسٹیمپ جس میں کوئی تبادلہ ہوا تھا، اس کا استعمال وقت کے ساتھ ایکسچینج ریٹس کو ٹریک کرنے کے لیے کیا جاتا ہے۔

Ethereum کانٹریکٹس کے سب سے بڑے گیس اخراجات میں سے ایک اسٹوریج ہے، جو کانٹریکٹ کی ایک کال سے دوسری کال تک برقرار رہتا ہے۔ ہر اسٹوریج سیل 256 بٹس طویل ہوتا ہے۔ لہذا تین متغیرات، reserve0، reserve1، اور blockTimestampLast، کو اس طرح مختص کیا گیا ہے کہ ایک ہی اسٹوریج ویلیو ان تینوں کو شامل کر سکے (112+112+32=256

1 uint public price0CumulativeLast;
2 uint public price1CumulativeLast;

یہ متغیرات ہر ٹوکن کے لیے مجموعی لاگت (ایک دوسرے کے لحاظ سے) کو ہولڈ کرتے ہیں۔ ان کا استعمال ایک مخصوص مدت کے دوران اوسط ایکسچینج ریٹ کا حساب لگانے کے لیے کیا جا سکتا ہے۔

1 uint public kLast; // reserve0 * reserve1، حالیہ ترین لیکویڈیٹی ایونٹ کے فوراً بعد کے مطابق

پیئر ایکسچینج (pair exchange) جس طرح token0 اور token1 کے درمیان ایکسچینج ریٹ کا فیصلہ کرتا ہے وہ یہ ہے کہ ٹریڈز کے دوران دونوں ریزرو کے ملٹیپل (multiple) کو مستقل رکھا جائے۔ 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 کی متعلقہ قدر (relative value) بڑھ جاتی ہے، اور اسی طرح اس کے برعکس بھی ہوتا ہے۔

لاک (Lock)

1 uint private unlocked = 1;

سیکیورٹی کمزوریوں کی ایک قسم ہے جو ری اینٹرنسی کے غلط استعمال (reentrancy abuse) (opens in a new tab) پر مبنی ہے۔ Uniswap کو صوابدیدی (arbitrary) ERC-20 ٹوکنز منتقل کرنے کی ضرورت ہوتی ہے، جس کا مطلب ہے ایسے ERC-20 کانٹریکٹس کو کال کرنا جو انہیں کال کرنے والی Uniswap مارکیٹ کا غلط استعمال کرنے کی کوشش کر سکتے ہیں۔ کانٹریکٹ کے حصے کے طور پر ایک 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 }

مرکزی فنکشن کے واپس آنے (returns) کے بعد، لاک کو ریلیز کر دیں۔

متفرق فنکشنز

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

یہ فنکشن کال کرنے والوں کو ایکسچینج کی موجودہ اسٹیٹ (state) فراہم کرتا ہے۔ غور کریں کہ Solidity فنکشنز متعدد ویلیوز واپس کر سکتے ہیں (opens in a new tab)۔

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

یہ اندرونی فنکشن ایکسچینج سے کسی اور کو ERC20 ٹوکنز کی ایک مقدار منتقل کرتا ہے۔ SELECTOR یہ بتاتا ہے کہ ہم جس فنکشن کو کال کر رہے ہیں وہ transfer(address,uint) ہے (اوپر تعریف دیکھیں)۔

ٹوکن فنکشن کے لیے انٹرفیس امپورٹ کرنے سے بچنے کے لیے، ہم ABI فنکشنز (opens in a new tab) میں سے کسی ایک کا استعمال کرتے ہوئے "دستی طور پر" کال بناتے ہیں۔

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

دو طریقے ہیں جن سے ERC-20 ٹرانسفر کال ناکامی کی اطلاع دے سکتی ہے:

  1. ریورٹ (Revert)۔ اگر کسی بیرونی کانٹریکٹ کی کال ریورٹ ہو جاتی ہے، تو بولین (boolean) ریٹرن ویلیو false ہوتی ہے
  2. عام طور پر ختم ہو لیکن ناکامی کی اطلاع دے۔ اس صورت میں ریٹرن ویلیو بفر کی لمبائی غیر صفر (non-zero) ہوتی ہے، اور جب اسے بولین ویلیو کے طور پر ڈی کوڈ کیا جاتا ہے تو یہ false ہوتی ہے

اگر ان میں سے کوئی بھی شرط پوری ہوتی ہے، تو ریورٹ کر دیں۔

ایونٹس (Events)

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

یہ دو ایونٹس اس وقت خارج (emit) ہوتے ہیں جب کوئی لیکویڈیٹی فراہم کنندہ یا تو لیکویڈیٹی جمع کرواتا ہے (Mint) یا اسے نکلواتا ہے (Burn)۔ دونوں صورتوں میں، جمع کروائے گئے یا نکلوائے گئے token0 اور token1 کی مقدار ایونٹ کا حصہ ہوتی ہے، نیز اس اکاؤنٹ کی شناخت بھی جس نے ہمیں کال کیا تھا (sender)۔ رقم نکلوانے کی صورت میں، ایونٹ میں وہ ہدف بھی شامل ہوتا ہے جس نے ٹوکنز وصول کیے (to)، جو ہو سکتا ہے کہ بھیجنے والے (sender) جیسا نہ ہو۔

1 event Swap(
2 address indexed sender,
3 uint amount0In,
4 uint amount1In,
5 uint amount0Out,
6 uint amount1Out,
7 address indexed to
8 );

یہ ایونٹ اس وقت خارج ہوتا ہے جب کوئی ٹریڈر ایک ٹوکن کو دوسرے سے سویپ کرتا ہے۔ ایک بار پھر، بھیجنے والا اور منزل (destination) ایک جیسے نہیں ہو سکتے۔ ہر ٹوکن یا تو ایکسچینج کو بھیجا جا سکتا ہے، یا اس سے وصول کیا جا سکتا ہے۔

1 event Sync(uint112 reserve0, uint112 reserve1);

آخر میں، Sync ہر بار اس وقت خارج ہوتا ہے جب ٹوکنز شامل کیے جاتے ہیں یا نکالے جاتے ہیں، قطع نظر اس کی وجہ کے، تاکہ تازہ ترین ریزرو کی معلومات (اور اس وجہ سے ایکسچینج ریٹ) فراہم کی جا سکے۔

سیٹ اپ فنکشنز

یہ فنکشنز اس وقت ایک بار کال کیے جانے چاہئیں جب نیا پیئر ایکسچینج سیٹ اپ کیا جائے۔

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

کنسٹرکٹر (constructor) اس بات کو یقینی بناتا ہے کہ ہم اس فیکٹری کے ایڈریس کا ٹریک رکھیں گے جس نے یہ پیئر بنایا ہے۔ یہ معلومات initialize اور فیکٹری فیس (اگر کوئی موجود ہو) کے لیے درکار ہوتی ہے۔

1 // ڈپلائمنٹ کے وقت فیکٹری کی طرف سے ایک بار کال کیا جاتا ہے
2 function initialize(address _token0, address _token1) external {
3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // کافی جانچ
4 token0 = _token0;
5 token1 = _token1;
6 }

یہ فنکشن فیکٹری کو (اور صرف فیکٹری کو) ان دو ERC-20 ٹوکنز کی وضاحت کرنے کی اجازت دیتا ہے جن کا یہ پیئر تبادلہ کرے گا۔

اندرونی اپ ڈیٹ فنکشنز

_update
1 // ریزرو کو اپ ڈیٹ کریں اور، فی بلاک پہلی کال پر، پرائس ایکومولیٹرز کو اپ ڈیٹ کریں
2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {

یہ فنکشن ہر بار اس وقت کال کیا جاتا ہے جب ٹوکنز جمع کروائے جاتے ہیں یا نکالے جاتے ہیں۔

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

اگر balance0 یا balance1 (uint256) میں سے کوئی بھی uint112(-1) (=2^112-1) سے زیادہ ہے (لہذا جب اسے uint112 میں تبدیل کیا جاتا ہے تو یہ اوور فلو ہو کر واپس 0 پر آ جاتا ہے) تو اوور فلو کو روکنے کے لیے _update کو جاری رکھنے سے انکار کر دیں۔ ایک عام ٹوکن کے ساتھ جسے 10^18 اکائیوں میں تقسیم کیا جا سکتا ہے، اس کا مطلب ہے کہ ہر ایکسچینج ہر ٹوکن کے تقریباً 5.1*10^15 تک محدود ہے۔ اب تک یہ کوئی مسئلہ نہیں رہا ہے۔

1 uint32 blockTimestamp = uint32(block.timestamp % 2**32);
2 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // اوور فلو مطلوب ہے
3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {

اگر گزرا ہوا وقت صفر نہیں ہے، تو اس کا مطلب ہے کہ ہم اس بلاک پر پہلی ایکسچینج ٹرانزیکشن ہیں۔ اس صورت میں، ہمیں کاسٹ ایکومولیٹرز (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 }

آخر میں، گلوبل متغیرات کو اپ ڈیٹ کریں اور ایک Sync ایونٹ خارج کریں۔

_mintFee
1 // اگر فیس آن ہے، تو sqrt(k) میں اضافے کے 1/6 کے برابر لیکویڈیٹی منٹ کریں
2 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {

Uniswap 2.0 میں ٹریڈرز مارکیٹ استعمال کرنے کے لیے 0.30% فیس ادا کرتے ہیں۔ اس فیس کا زیادہ تر حصہ (ٹریڈ کا 0.25%) ہمیشہ لیکویڈیٹی فراہم کنندگان کو جاتا ہے۔ بقیہ 0.05% یا تو لیکویڈیٹی فراہم کنندگان کو جا سکتا ہے یا فیکٹری کی طرف سے پروٹوکول فیس کے طور پر بتائے گئے ایڈریس پر، جو Uniswap کو ان کی ڈیولپمنٹ کی کوششوں کے لیے ادائیگی کرتا ہے۔

کیلکولیشنز (اور اس وجہ سے گیس کی لاگت) کو کم کرنے کے لیے، یہ فیس ہر ٹرانزیکشن کے بجائے صرف اس وقت کیلکولیٹ کی جاتی ہے جب پول میں لیکویڈیٹی شامل کی جاتی ہے یا ہٹائی جاتی ہے۔

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

فیکٹری کی فیس کی منزل (destination) پڑھیں۔ اگر یہ صفر ہے تو کوئی پروٹوکول فیس نہیں ہے اور اس فیس کا حساب لگانے کی ضرورت نہیں ہے۔

1 uint _kLast = kLast; // گیس کی بچت

kLast اسٹیٹ متغیر اسٹوریج میں واقع ہے، لہذا کانٹریکٹ کی مختلف کالز کے درمیان اس کی ایک ویلیو ہوگی۔ اسٹوریج تک رسائی اس وولیٹائل میموری (volatile memory) تک رسائی سے کہیں زیادہ مہنگی ہے جو کانٹریکٹ کی فنکشن کال ختم ہونے پر ریلیز ہو جاتی ہے، اس لیے ہم گیس بچانے کے لیے ایک اندرونی متغیر کا استعمال کرتے ہیں۔

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

لیکویڈیٹی فراہم کنندگان کو ان کا حصہ صرف ان کے لیکویڈیٹی ٹوکنز کی قدر میں اضافے (appreciation) سے ملتا ہے۔ لیکن پروٹوکول فیس کے لیے نئے لیکویڈیٹی ٹوکنز منٹ (mint) کرنے اور feeTo ایڈریس پر فراہم کرنے کی ضرورت ہوتی ہے۔

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

اگر کوئی نئی لیکویڈیٹی ہے جس پر پروٹوکول فیس وصول کی جانی ہے۔ آپ اسکوائر روٹ (square root) فنکشن کو اس مضمون میں آگے دیکھ سکتے ہیں۔

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

فیس کی یہ پیچیدہ کیلکولیشن وائٹ پیپر (opens in a new tab) کے صفحہ 5 پر بیان کی گئی ہے۔ ہم جانتے ہیں کہ جس وقت kLast کا حساب لگایا گیا تھا اور موجودہ وقت کے درمیان کوئی لیکویڈیٹی شامل یا ہٹائی نہیں گئی تھی (کیونکہ ہم یہ کیلکولیشن ہر بار لیکویڈیٹی شامل کرنے یا ہٹانے پر چلاتے ہیں، اس سے پہلے کہ یہ حقیقت میں تبدیل ہو)، لہذا reserve0 * reserve1 میں کوئی بھی تبدیلی ٹرانزیکشن فیس سے آنی چاہیے (ان کے بغیر ہم reserve0 * reserve1 کو مستقل رکھیں گے)۔

1 if (liquidity > 0) _mint(feeTo, liquidity);
2 }
3 }

اضافی لیکویڈیٹی ٹوکنز کو حقیقت میں بنانے اور انہیں feeTo کو تفویض کرنے کے لیے UniswapV2ERC20._mint فنکشن کا استعمال کریں۔

1 } else if (_kLast != 0) {
2 kLast = 0;
3 }
4 }

اگر کوئی فیس نہیں ہے تو kLast کو صفر پر سیٹ کریں (اگر یہ پہلے سے ایسا نہیں ہے)۔ جب یہ کانٹریکٹ لکھا گیا تھا تو ایک گیس ریفنڈ فیچر (opens in a new tab) موجود تھا جو کانٹریکٹس کی حوصلہ افزائی کرتا تھا کہ وہ اس اسٹوریج کو صفر کر کے Ethereum اسٹیٹ کے مجموعی سائز کو کم کریں جس کی انہیں ضرورت نہیں تھی۔ یہ کوڈ ممکن ہونے پر وہ ریفنڈ حاصل کرتا ہے۔

بیرونی طور پر قابل رسائی فنکشنز

نوٹ کریں کہ اگرچہ کوئی بھی ٹرانزیکشن یا کانٹریکٹ ان فنکشنز کو کال کر سکتا ہے، لیکن انہیں پیریفری (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; // گیس کی بچت، اسے یہاں بیان کیا جانا چاہیے کیونکہ totalSupply _mintFee میں اپ ڈیٹ ہو سکتی ہے
2 if (_totalSupply == 0) {
3 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
4 _mint(address(0), MINIMUM_LIQUIDITY); // پہلے MINIMUM_LIQUIDITY ٹوکنز کو مستقل طور پر لاک کریں

اگر یہ پہلی ڈپازٹ ہے، تو MINIMUM_LIQUIDITY ٹوکنز بنائیں اور انہیں لاک کرنے کے لیے ایڈریس صفر پر بھیجیں۔ انہیں کبھی بھی ریڈیم (redeem) نہیں کیا جا سکتا، جس کا مطلب ہے کہ پول کبھی بھی مکمل طور پر خالی نہیں ہوگا (یہ ہمیں کچھ جگہوں پر صفر سے تقسیم ہونے سے بچاتا ہے)۔ 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) {

یہ فنکشن اس وقت کال کیا جاتا ہے جب لیکویڈیٹی نکالی جاتی ہے اور مناسب لیکویڈیٹی ٹوکنز کو برن (burn) کرنے کی ضرورت ہوتی ہے۔ اسے بھی ایک پیریفری اکاؤنٹ سے کال کیا جانا چاہیے۔

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // گیس کی بچت
2 address _token0 = token0; // گیس کی بچت
3 address _token1 = token1; // گیس کی بچت
4 uint balance0 = IERC20(_token0).balanceOf(address(this));
5 uint balance1 = IERC20(_token1).balanceOf(address(this));
6 uint liquidity = balanceOf[address(this)];

پیریفری کانٹریکٹ نے کال سے پہلے برن کی جانے والی لیکویڈیٹی کو اس کانٹریکٹ میں منتقل کر دیا تھا۔ اس طرح ہم جانتے ہیں کہ کتنی لیکویڈیٹی برن کرنی ہے، اور ہم اس بات کو یقینی بنا سکتے ہیں کہ یہ برن ہو جائے۔

1 bool feeOn = _mintFee(_reserve0, _reserve1);
2 uint _totalSupply = totalSupply; // گیس کی بچت، اسے یہاں بیان کیا جانا چاہیے کیونکہ totalSupply _mintFee میں اپ ڈیٹ ہو سکتی ہے
3 amount0 = liquidity.mul(balance0) / _totalSupply; // بیلنس کا استعمال متناسب (pro-rata) تقسیم کو یقینی بناتا ہے
4 amount1 = liquidity.mul(balance1) / _totalSupply; // بیلنس کا استعمال متناسب (pro-rata) تقسیم کو یقینی بناتا ہے
5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');

لیکویڈیٹی فراہم کنندہ کو دونوں ٹوکنز کی مساوی قدر ملتی ہے۔ اس طرح ہم ایکسچینج ریٹ کو تبدیل نہیں کرتے۔

1 _burn(address(this), liquidity);
2 _safeTransfer(_token0, to, amount0);
3 _safeTransfer(_token1, to, amount1);
4 balance0 = IERC20(_token0).balanceOf(address(this));
5 balance1 = IERC20(_token1).balanceOf(address(this));
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 فنکشن کا عکس (mirror image) ہے۔

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 ایررز سے بچاتا ہے

لوکل متغیرات کو یا تو میموری میں اسٹور کیا جا سکتا ہے یا، اگر وہ بہت زیادہ نہ ہوں، تو براہ راست اسٹیک (stack) پر۔ اگر ہم تعداد کو محدود کر سکیں تاکہ ہم اسٹیک کا استعمال کریں تو ہم کم گیس استعمال کرتے ہیں۔ مزید تفصیلات کے لیے یلو پیپر، رسمی Ethereum کی خصوصیات (opens in a new tab)، صفحہ 26، مساوات 298 دیکھیں۔

1 address _token0 = token0;
2 address _token1 = token1;
3 require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
4 if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // پرامید طور پر (optimistically) ٹوکنز ٹرانسفر کریں
5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // پرامید طور پر (optimistically) ٹوکنز ٹرانسفر کریں

یہ ٹرانسفر پرامید (optimistic) ہے، کیونکہ ہم اس بات کا یقین ہونے سے پہلے ہی ٹرانسفر کر دیتے ہیں کہ تمام شرائط پوری ہو گئی ہیں۔ Ethereum میں یہ ٹھیک ہے کیونکہ اگر کال میں بعد میں شرائط پوری نہیں ہوتی ہیں تو ہم اسے اور اس کی پیدا کردہ کسی بھی تبدیلی کو ریورٹ کر دیتے ہیں۔

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

اگر درخواست کی جائے تو وصول کنندہ کو سویپ کے بارے میں مطلع کریں۔

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

موجودہ بیلنس حاصل کریں۔ پیریفری کانٹریکٹ ہمیں سویپ کے لیے کال کرنے سے پہلے ٹوکنز بھیجتا ہے۔ اس سے کانٹریکٹ کے لیے یہ چیک کرنا آسان ہو جاتا ہے کہ اسے دھوکہ نہیں دیا جا رہا ہے، ایک ایسا چیک جو بنیادی کانٹریکٹ میں ہونا ضروری ہے (کیونکہ ہمیں ہمارے پیریفری کانٹریکٹ کے علاوہ دیگر اینٹیٹیز کے ذریعے بھی کال کیا جا سکتا ہے)۔

1 uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
2 uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
3 require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
4 { // reserve{0,1}Adjusted کے لیے اسکوپ، stack too deep ایررز سے بچاتا ہے
5 uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
6 uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
7 require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');

یہ ایک سینیٹی چیک (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

یہ ممکن ہے کہ اصل بیلنس ان ریزرو کے ساتھ آؤٹ آف سنک (out of sync) ہو جائیں جو پیئر ایکسچینج کے خیال میں اس کے پاس ہیں۔ کانٹریکٹ کی رضامندی کے بغیر ٹوکنز نکالنے کا کوئی طریقہ نہیں ہے، لیکن ڈپازٹس ایک الگ معاملہ ہے۔ ایک اکاؤنٹ 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، ایک میپنگ (mapping) ہے جو ایک پیئر ایکسچینج کانٹریکٹ کی شناخت ان دو ERC-20 ٹوکنز کی بنیاد پر کرتا ہے جن کا یہ تبادلہ کرتا ہے۔ ERC-20 ٹوکنز کی شناخت ان کانٹریکٹس کے ایڈریسز سے ہوتی ہے جو انہیں نافذ کرتے ہیں، لہذا کیز (keys) اور ویلیو سبھی ایڈریسز ہیں۔ اس پیئر ایکسچینج کا ایڈریس حاصل کرنے کے لیے جو آپ کو tokenA سے tokenB میں تبدیل کرنے دیتا ہے، آپ getPair[<tokenA address>][<tokenB address>] (یا اس کے برعکس) استعمال کرتے ہیں۔

دوسرا متغیر، allPairs، ایک اری (array) ہے جس میں اس فیکٹری کے ذریعے بنائے گئے پیئر ایکسچینجز کے تمام ایڈریسز شامل ہیں۔ Ethereum میں آپ کسی میپنگ کے مواد پر ایٹریٹ (iterate) نہیں کر سکتے، یا تمام کیز کی فہرست حاصل نہیں کر سکتے، لہذا یہ متغیر یہ جاننے کا واحد طریقہ ہے کہ یہ فیکٹری کن ایکسچینجز کا انتظام کرتی ہے۔

نوٹ: آپ کسی میپنگ کی تمام کیز پر ایٹریٹ کیوں نہیں کر سکتے اس کی وجہ یہ ہے کہ کانٹریکٹ ڈیٹا اسٹوریج مہنگا ہوتا ہے، لہذا ہم اس کا جتنا کم استعمال کریں اتنا ہی بہتر ہے، اور ہم اسے جتنی کم بار تبدیل کریں اتنا ہی بہتر ہے۔ آپ ایسی میپنگز بنا سکتے ہیں جو ایٹریشن کو سپورٹ کرتی ہوں (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);

ہم چاہتے ہیں کہ نئے ایکسچینج کا ایڈریس ڈیٹرمنسٹک (deterministic) ہو، تاکہ اس کا حساب پہلے سے آف چین (offchain) لگایا جا سکے (یہ لیئر 2 ٹرانزیکشنز کے لیے مفید ہو سکتا ہے)۔ ایسا کرنے کے لیے ہمیں ٹوکن ایڈریسز کی ایک مستقل ترتیب کی ضرورت ہے، قطع نظر اس ترتیب کے جس میں ہم نے انہیں وصول کیا ہے، اس لیے ہم انہیں یہاں ترتیب (sort) دیتے ہیں۔

1 require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
2 require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // ایک ہی جانچ کافی ہے

بڑے لیکویڈیٹی پولز چھوٹے پولز سے بہتر ہوتے ہیں، کیونکہ ان کی قیمتیں زیادہ مستحکم ہوتی ہیں۔ ہم ٹوکنز کے فی پیئر کے لیے ایک سے زیادہ لیکویڈیٹی پول نہیں رکھنا چاہتے۔ اگر پہلے سے ہی کوئی ایکسچینج موجود ہے، تو اسی پیئر کے لیے دوسرا بنانے کی ضرورت نہیں ہے۔

1 bytes memory bytecode = type(UniswapV2Pair).creationCode;

ایک نیا کانٹریکٹ بنانے کے لیے ہمیں اس کوڈ کی ضرورت ہوتی ہے جو اسے بناتا ہے (کنسٹرکٹر فنکشن اور وہ کوڈ جو اصل کانٹریکٹ کے EVM بائٹ کوڈ کو میموری میں لکھتا ہے، دونوں)۔ عام طور پر Solidity میں ہم صرف addr = new <name of contract>(<constructor parameters>) استعمال کرتے ہیں اور کمپائلر ہمارے لیے ہر چیز کا خیال رکھتا ہے، لیکن ایک ڈیٹرمنسٹک کانٹریکٹ ایڈریس حاصل کرنے کے لیے ہمیں CREATE2 اوپ کوڈ (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 ہوتا ہے، پھر مالک کی جانب سے پرمٹ (permit) جمع کرواتا ہے۔

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;

یہ ہیش ٹرانزیکشن کی قسم کا شناخت کنندہ (identifier) (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 کا نانس استعمال کیے گئے آخری نانس سے ایک زیادہ نہیں ہے، تو ہم فرض کرتے ہیں کہ یہ غلط (invalid) ہے۔

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

یہ چین شناخت کنندہ (chain identifier) (opens in a new tab) کو بازیافت کرنے کا کوڈ ہے۔ یہ ایک EVM اسمبلی ڈائلیکٹ (dialect) استعمال کرتا ہے جسے Yul (opens in a new tab) کہا جاتا ہے۔ نوٹ کریں کہ 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 {

یہ وہ فنکشن ہے جو پرمیشنز (permissions) کو نافذ کرتا ہے۔ یہ متعلقہ فیلڈز، اور دستخط (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) کے طور پر سمجھیں۔

پیریفیری (Periphery) کنٹریکٹس

پیریفیری کنٹریکٹس Uniswap کے لیے API (ایپلیکیشن پروگرام انٹرفیس) ہیں۔ یہ بیرونی کالز کے لیے دستیاب ہیں، چاہے وہ دیگر کنٹریکٹس سے ہوں یا ڈی سینٹرلائزڈ ایپلیکیشنز سے۔ آپ براہ راست کور کنٹریکٹس کو کال کر سکتے ہیں، لیکن یہ زیادہ پیچیدہ ہے اور اگر آپ غلطی کرتے ہیں تو آپ کی ویلیو ضائع ہو سکتی ہے۔ کور کنٹریکٹس میں صرف یہ یقینی بنانے کے لیے ٹیسٹ شامل ہوتے ہیں کہ ان کے ساتھ دھوکہ نہ ہو، کسی اور کے لیے کوئی سینیٹی چیکس (sanity checks) نہیں ہوتے۔ وہ پیریفیری میں ہوتے ہیں تاکہ ضرورت کے مطابق انہیں اپ ڈیٹ کیا جا سکے۔

UniswapV2Router01.sol

اس کنٹریکٹ (opens in a new tab) میں مسائل ہیں، اور اسے مزید استعمال نہیں کیا جانا چاہیے (opens in a new tab)۔ خوش قسمتی سے، پیریفیری کنٹریکٹس اسٹیٹ لیس (stateless) ہوتے ہیں اور ان میں کوئی اثاثے نہیں ہوتے، اس لیے اسے متروک (deprecate) کرنا اور لوگوں کو اس کے متبادل، UniswapV2Router02، کے استعمال کا مشورہ دینا آسان ہے۔

UniswapV2Router02.sol

زیادہ تر صورتوں میں آپ Uniswap کو اس کنٹریکٹ (opens in a new tab) کے ذریعے استعمال کریں گے۔ آپ اسے استعمال کرنے کا طریقہ یہاں (opens in a new tab) دیکھ سکتے ہیں۔

1pragma solidity =0.6.6;
2
3import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
4import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
5
6import './interfaces/IUniswapV2Router02.sol';
7import './libraries/UniswapV2Library.sol';
8import './libraries/SafeMath.sol';
9import './interfaces/IERC20.sol';
10import './interfaces/IWETH.sol';
سب دکھائیں

ان میں سے زیادہ تر کا ہم نے پہلے سامنا کیا ہے، یا وہ کافی واضح ہیں۔ اس میں ایک استثنیٰ IWETH.sol ہے۔ Uniswap v2 کسی بھی ERC-20 ٹوکنز کے جوڑے کے تبادلے کی اجازت دیتا ہے، لیکن ایتھر (ETH) خود ایک ERC-20 ٹوکن نہیں ہے۔ یہ اس معیار سے پہلے کا ہے اور اسے منفرد میکانزم کے ذریعے منتقل کیا جاتا ہے۔ ERC-20 ٹوکنز پر لاگو ہونے والے کنٹریکٹس میں ETH کے استعمال کو فعال کرنے کے لیے لوگوں نے ریپڈ ایتھر (WETH) (opens in a new tab) کنٹریکٹ متعارف کرایا۔ آپ اس کنٹریکٹ کو ETH بھیجتے ہیں، اور یہ آپ کو اتنی ہی مقدار میں WETH منٹ (mint) کر کے دیتا ہے۔ یا آپ WETH کو برن (burn) کر کے واپس ETH حاصل کر سکتے ہیں۔

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

راؤٹر کو یہ جاننے کی ضرورت ہوتی ہے کہ کون سی فیکٹری استعمال کرنی ہے، اور جن ٹرانزیکشنز کے لیے WETH درکار ہے ان کے لیے کون سا WETH کنٹریکٹ استعمال کرنا ہے۔ یہ ویلیوز ناقابلِ تبدیلی (immutable) (opens in a new tab) ہوتی ہیں، جس کا مطلب ہے کہ انہیں صرف کنسٹرکٹر (constructor) میں سیٹ کیا جا سکتا ہے۔ اس سے صارفین کو یہ اعتماد ملتا ہے کہ کوئی بھی انہیں تبدیل کر کے کم ایماندار کنٹریکٹس کی طرف اشارہ نہیں کر سکے گا۔

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

یہ موڈیفائر (modifier) اس بات کو یقینی بناتا ہے کہ وقت کی پابندی والی ٹرانزیکشنز ("اگر ہو سکے تو وقت Y سے پہلے X کریں") اپنی مقررہ مدت کے بعد نہ ہوں۔

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

کنسٹرکٹر صرف ناقابلِ تبدیلی اسٹیٹ ویری ایبلز کو سیٹ کرتا ہے۔

1 receive() external payable {
2 assert(msg.sender == WETH); // صرف WETH کنٹریکٹ سے فال بیک کے ذریعے ETH قبول کریں
3 }

یہ فنکشن اس وقت کال کیا جاتا ہے جب ہم WETH کنٹریکٹ سے ٹوکنز کو واپس ETH میں ریڈیم (redeem) کرتے ہیں۔ صرف وہی WETH کنٹریکٹ جو ہم استعمال کرتے ہیں، ایسا کرنے کا مجاز ہے۔

لیکویڈیٹی شامل کریں

یہ فنکشنز پیئر ایکسچینج (pair exchange) میں ٹوکنز شامل کرتے ہیں، جس سے لیکویڈیٹی پول میں اضافہ ہوتا ہے۔

1
2 // **** لیکویڈیٹی شامل کریں ****
3 function _addLiquidity(

یہ فنکشن A اور B ٹوکنز کی اس مقدار کا حساب لگانے کے لیے استعمال ہوتا ہے جو پیئر ایکسچینج میں جمع کی جانی چاہیے۔

1 address tokenA,
2 address tokenB,

یہ ERC-20 ٹوکن کنٹریکٹس کے ایڈریسز ہیں۔

1 uint amountADesired,
2 uint amountBDesired,

یہ وہ مقداریں ہیں جو لیکویڈیٹی فراہم کنندہ جمع کرنا چاہتا ہے۔ یہ A اور B کی جمع کی جانے والی زیادہ سے زیادہ مقداریں بھی ہیں۔

1 uint amountAMin,
2 uint amountBMin

یہ جمع کرنے کے لیے کم از کم قابلِ قبول مقداریں ہیں۔ اگر ٹرانزیکشن ان مقداروں یا اس سے زیادہ کے ساتھ نہیں ہو سکتی، تو اسے ریورٹ (revert) کر دیں۔ اگر آپ یہ فیچر نہیں چاہتے ہیں، تو بس صفر (zero) درج کریں۔

لیکویڈیٹی فراہم کرنے والے عام طور پر کم از کم مقدار اس لیے بتاتے ہیں کیونکہ وہ ٹرانزیکشن کو موجودہ ایکسچینج ریٹ کے قریب تک محدود رکھنا چاہتے ہیں۔ اگر ایکسچینج ریٹ میں بہت زیادہ اتار چڑھاؤ آتا ہے تو اس کا مطلب ایسی خبریں ہو سکتی ہیں جو بنیادی ویلیوز کو تبدیل کر دیں، اور وہ دستی طور پر فیصلہ کرنا چاہتے ہیں کہ کیا کرنا ہے۔

مثال کے طور پر، ایک ایسی صورتحال کا تصور کریں جہاں ایکسچینج ریٹ ایک کے بدلے ایک (one to one) ہے اور لیکویڈیٹی فراہم کنندہ یہ ویلیوز بتاتا ہے:

پیرامیٹر (Parameter)ویلیو (Value)
amountADesired1000
amountBDesired1000
amountAMin900
amountBMin800

جب تک ایکسچینج ریٹ 0.9 اور 1.25 کے درمیان رہتا ہے، ٹرانزیکشن مکمل ہو جاتی ہے۔ اگر ایکسچینج ریٹ اس حد سے باہر نکل جاتا ہے، تو ٹرانزیکشن منسوخ ہو جاتی ہے۔

اس احتیاط کی وجہ یہ ہے کہ ٹرانزیکشنز فوری نہیں ہوتیں، آپ انہیں جمع کراتے ہیں اور بالآخر ایک ویلیڈیٹر انہیں ایک بلاک میں شامل کرتا ہے (جب تک کہ آپ کی گیس کی قیمت بہت کم نہ ہو، جس صورت میں آپ کو اسے اوور رائٹ کرنے کے لیے اسی نانس (nonce) اور زیادہ گیس کی قیمت کے ساتھ ایک اور ٹرانزیکشن جمع کرانی ہوگی)۔ آپ اس بات کو کنٹرول نہیں کر سکتے کہ جمع کرانے اور شامل ہونے کے درمیانی وقفے میں کیا ہوتا ہے۔

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

یہ فنکشن وہ مقداریں واپس کرتا ہے جو لیکویڈیٹی فراہم کنندہ کو جمع کرانی چاہئیں تاکہ ریزرو (reserves) کے درمیان موجودہ تناسب کے برابر تناسب برقرار رہے۔

1 // اگر پیئر (pair) ابھی تک موجود نہیں ہے تو اسے بنائیں
2 if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
3 IUniswapV2Factory(factory).createPair(tokenA, tokenB);
4 }

اگر اس ٹوکن جوڑے کے لیے ابھی تک کوئی ایکسچینج نہیں ہے، تو اسے بنائیں۔

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

جوڑے میں موجودہ ریزرو حاصل کریں۔

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

اگر موجودہ ریزرو خالی ہیں تو یہ ایک نیا پیئر ایکسچینج ہے۔ جمع کی جانے والی مقداریں بالکل وہی ہونی چاہئیں جو لیکویڈیٹی فراہم کنندہ فراہم کرنا چاہتا ہے۔

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

اگر ہمیں یہ دیکھنے کی ضرورت ہے کہ مقداریں کیا ہوں گی، تو ہم اس فنکشن (opens in a new tab) کا استعمال کرتے ہوئے بہترین (optimal) مقدار حاصل کرتے ہیں۔ ہم موجودہ ریزرو جیسا ہی تناسب چاہتے ہیں۔

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

اگر amountBOptimal اس مقدار سے کم ہے جو لیکویڈیٹی فراہم کنندہ جمع کرنا چاہتا ہے تو اس کا مطلب ہے کہ ٹوکن B اس وقت اس سے زیادہ قیمتی ہے جتنا لیکویڈیٹی جمع کرنے والا سوچتا ہے، اس لیے کم مقدار درکار ہے۔

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

اگر بہترین B مقدار مطلوبہ B مقدار سے زیادہ ہے تو اس کا مطلب ہے کہ B ٹوکنز اس وقت اس سے کم قیمتی ہیں جتنا لیکویڈیٹی جمع کرنے والا سوچتا ہے، اس لیے زیادہ مقدار درکار ہے۔ تاہم، مطلوبہ مقدار زیادہ سے زیادہ حد ہے، اس لیے ہم ایسا نہیں کر سکتے۔ اس کے بجائے ہم B ٹوکنز کی مطلوبہ مقدار کے لیے A ٹوکنز کی بہترین تعداد کا حساب لگاتے ہیں۔

ان سب کو ملا کر ہمیں یہ گراف ملتا ہے۔ فرض کریں کہ آپ ایک ہزار A ٹوکنز (نیلی لکیر) اور ایک ہزار B ٹوکنز (سرخ لکیر) جمع کرنے کی کوشش کر رہے ہیں۔ x محور (x axis) ایکسچینج ریٹ، A/B ہے۔ اگر x=1 ہے، تو ان کی ویلیو برابر ہے اور آپ ہر ایک کے ایک ہزار جمع کرتے ہیں۔ اگر x=2 ہے، تو A کی ویلیو B سے دگنی ہے (آپ کو ہر A ٹوکن کے لیے دو B ٹوکن ملتے ہیں) اس لیے آپ ایک ہزار B ٹوکنز جمع کرتے ہیں، لیکن صرف 500 A ٹوکنز۔ اگر x=0.5 ہے، تو صورتحال الٹ جاتی ہے، ایک ہزار A ٹوکنز اور پانچ سو B ٹوکنز۔

گراف

آپ براہ راست کور کنٹریکٹ میں لیکویڈیٹی جمع کر سکتے ہیں (UniswapV2Pair::mint (opens in a new tab) کا استعمال کرتے ہوئے)، لیکن کور کنٹریکٹ صرف یہ چیک کرتا ہے کہ اس کے ساتھ خود دھوکہ تو نہیں ہو رہا، اس لیے اگر آپ کی ٹرانزیکشن جمع کرانے اور اس کے ایگزیکیوٹ ہونے کے درمیانی وقت میں ایکسچینج ریٹ تبدیل ہو جاتا ہے تو آپ کو ویلیو کھونے کا خطرہ ہوتا ہے۔ اگر آپ پیریفیری کنٹریکٹ استعمال کرتے ہیں، تو یہ اس مقدار کا حساب لگاتا ہے جو آپ کو جمع کرانی چاہیے اور اسے فوری طور پر جمع کر دیتا ہے، اس لیے ایکسچینج ریٹ تبدیل نہیں ہوتا اور آپ کا کچھ نقصان نہیں ہوتا۔

1 function addLiquidity(
2 address tokenA,
3 address tokenB,
4 uint amountADesired,
5 uint amountBDesired,
6 uint amountAMin,
7 uint amountBMin,
8 address to,
9 uint deadline
سب دکھائیں

اس فنکشن کو لیکویڈیٹی جمع کرنے کے لیے ایک ٹرانزیکشن کے ذریعے کال کیا جا سکتا ہے۔ زیادہ تر پیرامیٹرز اوپر دیے گئے _addLiquidity کی طرح ہی ہیں، سوائے دو کے:

. to وہ ایڈریس ہے جسے پول میں لیکویڈیٹی فراہم کنندہ کا حصہ دکھانے کے لیے نئے لیکویڈیٹی ٹوکنز منٹ کر کے دیے جاتے ہیں . deadline ٹرانزیکشن پر وقت کی حد ہے

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

ہم اصل میں جمع کی جانے والی مقداروں کا حساب لگاتے ہیں اور پھر لیکویڈیٹی پول کا ایڈریس تلاش کرتے ہیں۔ گیس بچانے کے لیے ہم یہ فیکٹری سے پوچھ کر نہیں کرتے، بلکہ لائبریری فنکشن pairFor کا استعمال کرتے ہیں (نیچے لائبریریز میں دیکھیں)

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

صارف سے ٹوکنز کی درست مقدار پیئر ایکسچینج میں منتقل کریں۔

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

اس کے بدلے میں to ایڈریس کو پول کی جزوی ملکیت کے لیے لیکویڈیٹی ٹوکنز دیں۔ کور کنٹریکٹ کا mint فنکشن دیکھتا ہے کہ اس کے پاس کتنے اضافی ٹوکنز ہیں (پچھلی بار لیکویڈیٹی تبدیل ہونے کے مقابلے میں) اور اسی کے مطابق لیکویڈیٹی منٹ کرتا ہے۔

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

جب کوئی لیکویڈیٹی فراہم کنندہ Token/ETH پیئر ایکسچینج کو لیکویڈیٹی فراہم کرنا چاہتا ہے، تو کچھ اختلافات ہوتے ہیں۔ کنٹریکٹ لیکویڈیٹی فراہم کنندہ کے لیے ETH کو ریپ (wrap) کرنے کا کام سنبھالتا ہے۔ یہ بتانے کی ضرورت نہیں ہے کہ صارف کتنے ETH جمع کرنا چاہتا ہے، کیونکہ صارف انہیں صرف ٹرانزیکشن کے ساتھ بھیجتا ہے (یہ مقدار msg.value میں دستیاب ہوتی ہے)۔

1 uint amountTokenMin,
2 uint amountETHMin,
3 address to,
4 uint 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 میں ریپ کیا گیا ہے۔ اس کا مطلب یہ ہے کہ اگر ٹرانسفر ناکام ہو جاتا ہے تو یہ کنٹریکٹ کال بھی ناکام ہو جاتی ہے، اور اس لیے ریپنگ (wrapping) حقیقت میں نہیں ہوتی۔

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); // پیئر (pair) کو لیکویڈیٹی بھیجیں
3 (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);

کور کنٹریکٹ کا burn فنکشن صارف کو ٹوکنز واپس کرنے کا کام سنبھالتا ہے۔

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

جب کوئی فنکشن متعدد ویلیوز واپس کرتا ہے، لیکن ہمیں ان میں سے صرف کچھ میں دلچسپی ہوتی ہے، تو ہم اس طرح صرف وہی ویلیوز حاصل کرتے ہیں۔ گیس کے لحاظ سے یہ کسی ویلیو کو پڑھنے اور اسے کبھی استعمال نہ کرنے سے کچھ سستا ہے۔

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

مقداروں کو اس طریقے سے تبدیل کریں جس طرح کور کنٹریکٹ انہیں واپس کرتا ہے (کم ایڈریس والا ٹوکن پہلے) اس طریقے میں جس کی صارف توقع کرتا ہے (tokenA اور tokenB کے مطابق)۔

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

پہلے ٹرانسفر کرنا اور پھر اس کے جائز ہونے کی تصدیق کرنا ٹھیک ہے، کیونکہ اگر یہ جائز نہیں ہے تو ہم تمام اسٹیٹ تبدیلیوں کو ریورٹ کر دیں گے۔

1 function removeLiquidityETH(
2 address token,
3 uint liquidity,
4 uint amountTokenMin,
5 uint amountETHMin,
6 address to,
7 uint 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 }
سب دکھائیں

یہ فنکشنز میٹا ٹرانزیکشنز (meta-transactions) کو ریلے کرتے ہیں تاکہ ایتھر کے بغیر صارفین کو پرمٹ میکانزم (permit mechanism) کا استعمال کرتے ہوئے پول سے نکالنے کی اجازت مل سکے۔

1
2 // **** لیکویڈیٹی ہٹائیں (fee-on-transfer ٹوکنز کو سپورٹ کرتے ہوئے) ****
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 }
سب دکھائیں

آخری فنکشن اسٹوریج فیس کو میٹا ٹرانزیکشنز کے ساتھ ملاتا ہے۔

ٹریڈ (Trade)

1 // **** سویپ (SWAP) ****
2 // اس کے لیے ضروری ہے کہ ابتدائی رقم پہلے ہی پہلے پیئر (pair) کو بھیجی جا چکی ہو
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 کی اصل سرمایہ کاری)۔
مرحلہ (Step)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;

کیا یہ آخری ایکسچینج ہے؟ اگر ایسا ہے، تو ٹریڈ کے لیے موصول ہونے والے ٹوکنز کو منزل (destination) پر بھیجیں۔ اگر نہیں، تو اسے اگلے پیئر ایکسچینج میں بھیجیں۔

1
2 IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
3 amount0Out, amount1Out, to, new bytes(0)
4 );
5 }
6 }

دراصل ٹوکنز کو سویپ کرنے کے لیے پیئر ایکسچینج کو کال کریں۔ ہمیں ایکسچینج کے بارے میں بتانے کے لیے کسی کال بیک (callback) کی ضرورت نہیں ہے، اس لیے ہم اس فیلڈ میں کوئی بائٹس (bytes) نہیں بھیجتے۔

1 function swapExactTokensForTokens(

یہ فنکشن ٹریڈرز کے ذریعے براہ راست ایک ٹوکن کو دوسرے کے لیے سویپ کرنے کے لیے استعمال ہوتا ہے۔

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

اس پیرامیٹر میں ERC-20 کنٹریکٹس کے ایڈریسز شامل ہیں۔ جیسا کہ اوپر وضاحت کی گئی ہے، یہ ایک ایرے (array) ہے کیونکہ آپ کو اپنے موجودہ اثاثے سے مطلوبہ اثاثے تک پہنچنے کے لیے کئی پیئر ایکسچینجز سے گزرنا پڑ سکتا ہے۔

سولیڈیٹی (Solidity) میں ایک فنکشن پیرامیٹر کو memory یا calldata میں اسٹور کیا جا سکتا ہے۔ اگر فنکشن کنٹریکٹ کا انٹری پوائنٹ ہے، جسے براہ راست کسی صارف (ٹرانزیکشن کا استعمال کرتے ہوئے) یا کسی مختلف کنٹریکٹ سے کال کیا گیا ہے، تو پیرامیٹر کی ویلیو براہ راست کال ڈیٹا سے لی جا سکتی ہے۔ اگر فنکشن کو اندرونی طور پر کال کیا جاتا ہے، جیسا کہ اوپر _swap، تو پیرامیٹرز کو memory میں اسٹور کرنا پڑتا ہے۔ کال کیے گئے کنٹریکٹ کے نقطہ نظر سے calldata صرف پڑھنے کے لیے (read only) ہے۔

اسکیلر (scalar) اقسام جیسے uint یا address کے ساتھ کمپائلر ہمارے لیے اسٹوریج کے انتخاب کو سنبھالتا ہے، لیکن ایرے (arrays) کے ساتھ، جو طویل اور زیادہ مہنگے ہوتے ہیں، ہم استعمال ہونے والے اسٹوریج کی قسم بتاتے ہیں۔

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

ریٹرن ویلیوز ہمیشہ میموری میں واپس کی جاتی ہیں۔

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

ہر سویپ میں خریدی جانے والی مقدار کا حساب لگائیں۔ اگر نتیجہ اس کم از کم مقدار سے کم ہے جسے ٹریڈر قبول کرنے کے لیے تیار ہے، تو ٹرانزیکشن کو ریورٹ کر دیں۔

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

آخر میں، ابتدائی ERC-20 ٹوکن کو پہلے پیئر ایکسچینج کے اکاؤنٹ میں منتقل کریں اور _swap کو کال کریں۔ یہ سب ایک ہی ٹرانزیکشن میں ہو رہا ہے، اس لیے پیئر ایکسچینج جانتا ہے کہ کوئی بھی غیر متوقع ٹوکنز اس ٹرانسفر کا حصہ ہیں۔

1 function swapTokensForExactTokens(
2 uint amountOut,
3 uint amountInMax,
4 address[] calldata path,
5 address to,
6 uint 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، ایک ٹریڈر کو ان پٹ ٹوکنز کی صحیح تعداد بتانے کی اجازت دیتا ہے جو وہ دینے کے لیے تیار ہے اور آؤٹ پٹ ٹوکنز کی کم از کم تعداد جو وہ بدلے میں وصول کرنے کے لیے تیار ہے۔ یہ فنکشن ریورس سویپ (reverse swap) کرتا ہے، یہ ٹریڈر کو آؤٹ پٹ ٹوکنز کی وہ تعداد بتانے دیتا ہے جو وہ چاہتا ہے، اور ان پٹ ٹوکنز کی زیادہ سے زیادہ تعداد جو وہ ان کے لیے ادا کرنے کو تیار ہے۔

دونوں صورتوں میں، ٹریڈر کو پہلے اس پیریفیری کنٹریکٹ کو الاؤنس (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 }
سب دکھائیں

ان چاروں ویریئنٹس (variants) میں ETH اور ٹوکنز کے درمیان ٹریڈنگ شامل ہے۔ فرق صرف اتنا ہے کہ ہم یا تو ٹریڈر سے ETH وصول کرتے ہیں اور اسے WETH منٹ کرنے کے لیے استعمال کرتے ہیں، یا ہم پاتھ میں آخری ایکسچینج سے WETH وصول کرتے ہیں اور اسے برن کرتے ہیں، اور نتیجے میں ملنے والا ETH ٹریڈر کو واپس بھیج دیتے ہیں۔

1 // **** سویپ (fee-on-transfer ٹوکنز کو سپورٹ کرتے ہوئے) ****
2 // اس کے لیے ضروری ہے کہ ابتدائی رقم پہلے ہی پہلے پیئر (pair) کو بھیجی جا چکی ہو
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}
سب دکھائیں

یہ فنکشنز صرف پراکسیز (proxies) ہیں جو UniswapV2Library فنکشنز کو کال کرتے ہیں۔

UniswapV2Migrator.sol

یہ کنٹریکٹ پرانے v1 سے v2 میں ایکسچینجز کو مائیگریٹ (migrate) کرنے کے لیے استعمال کیا گیا تھا۔ اب چونکہ وہ مائیگریٹ ہو چکے ہیں، اس لیے یہ مزید متعلقہ نہیں ہے۔

لائبریریاں

SafeMath لائبریری (opens in a new tab) کی دستاویزات بہت اچھی طرح سے موجود ہیں، اس لیے اسے یہاں دستاویزی شکل دینے کی ضرورت نہیں ہے۔

ریاضی

اس لائبریری میں ریاضی کے کچھ ایسے فنکشنز شامل ہیں جن کی عام طور پر 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;
سب دکھائیں

x کو ایک تخمینے کے طور پر شروع کریں جو مربع جزر (square root) سے زیادہ ہو (یہی وجہ ہے کہ ہمیں 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;

ہمیں کبھی بھی صفر کے مربع جزر کی ضرورت نہیں ہونی چاہیے۔ ایک، دو، اور تین کے مربع جزر تقریباً ایک ہوتے ہیں (ہم انٹیجرز (integers) استعمال کرتے ہیں، اس لیے ہم کسر (fraction) کو نظر انداز کر دیتے ہیں)۔

1 }
2 }
3}

فکسڈ پوائنٹ فریکشنز (UQ112x112)

یہ لائبریری کسروں (fractions) کو ہینڈل کرتی ہے، جو عام طور پر Ethereum کے حساب کتاب کا حصہ نہیں ہوتیں۔ یہ عدد x کو x*2^112 کے طور پر انکوڈ کر کے ایسا کرتی ہے۔ اس سے ہم اصل اضافے (addition) اور تفریق (subtraction) کے 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) کے لیے انکوڈنگ ہے۔

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 // ترتیب شدہ ٹوکن ایڈریسز واپس کرتا ہے، جو اس ترتیب میں ترتیب دیے گئے پیئرز (pairs) سے ریٹرن ویلیوز کو ہینڈل کرنے کے لیے استعمال ہوتا ہے
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 // بغیر کسی بیرونی کال کے پیئر (pair) کے لیے CREATE2 ایڈریس کا حساب لگاتا ہے
2 function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
3 (address token0, address token1) = sortTokens(tokenA, tokenB);
4 pair = address(uint(keccak256(abi.encodePacked(
5 hex'ff',
6 factory,
7 keccak256(abi.encodePacked(token0, token1)),
8 hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init کوڈ ہیش
9 ))));
10 }
سب دکھائیں

یہ فنکشن دونوں ٹوکنز کے لیے پیئر ایکسچینج کے ایڈریس کا حساب لگاتا ہے۔ یہ کنٹریکٹ CREATE2 opcode (opens in a new tab) کا استعمال کرتے ہوئے بنایا گیا ہے، اس لیے اگر ہم اس کے استعمال کردہ پیرامیٹرز کو جانتے ہوں تو ہم اسی الگورتھم کا استعمال کرتے ہوئے ایڈریس کا حساب لگا سکتے ہیں۔ یہ فیکٹری سے پوچھنے کی نسبت بہت سستا ہے، اور

1 // پیئر (pair) کے لیے ریزرو حاصل کرتا ہے اور انہیں ترتیب دیتا ہے
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 }

یہ فنکشن آپ کو ٹوکن B کی وہ مقدار بتاتا ہے جو آپ کو ٹوکن A کے بدلے میں ملے گی اگر اس میں کوئی فیس شامل نہ ہو۔ یہ حساب اس بات کو مدنظر رکھتا ہے کہ ٹرانسفر سے ایکسچینج ریٹ تبدیل ہوتا ہے۔

1 // کسی اثاثے کی ان پٹ مقدار اور پیئر ریزرو دیے جانے پر، دوسرے اثاثے کی زیادہ سے زیادہ آؤٹ پٹ مقدار واپس کرتا ہے
2 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {

اوپر دیا گیا quote فنکشن بہت اچھا کام کرتا ہے اگر پیئر ایکسچینج استعمال کرنے کی کوئی فیس نہ ہو۔ تاہم، اگر 0.3% ایکسچینج فیس ہو تو آپ کو ملنے والی اصل رقم کم ہوتی ہے۔ یہ فنکشن ایکسچینج فیس کے بعد کی رقم کا حساب لگاتا ہے۔

1
2 require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
3 require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
4 uint amountInWithFee = amountIn.mul(997);
5 uint numerator = amountInWithFee.mul(reserveOut);
6 uint denominator = reserveIn.mul(1000).add(amountInWithFee);
7 amountOut = numerator / denominator;
8 }

Solidity مقامی طور پر کسروں (fractions) کو ہینڈل نہیں کرتی، اس لیے ہم رقم کو صرف 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 // کسی بھی تعداد میں پیئرز (pairs) پر جڑے ہوئے (chained) getAmountOut کیلکولیشنز انجام دیتا ہے
3 function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
4 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
5 amounts = new uint[](path.length);
6 amounts[0] = amountIn;
7 for (uint i; i < path.length - 1; i++) {
8 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
9 amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
10 }
11 }
12
13 // کسی بھی تعداد میں پیئرز (pairs) پر جڑے ہوئے (chained) getAmountIn کیلکولیشنز انجام دیتا ہے
14 function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
15 require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
16 amounts = new uint[](path.length);
17 amounts[amounts.length - 1] = amountOut;
18 for (uint i = path.length - 1; i > 0; i--) {
19 (uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
20 amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
21 }
22 }
23}
سب دکھائیں

یہ دونوں فنکشنز ان ویلیوز کی شناخت کو ہینڈل کرتے ہیں جب کئی پیئر ایکسچینجز سے گزرنا ضروری ہو۔

ٹرانسفر ہیلپر

یہ لائبریری (opens in a new tab) ERC-20 اور Ethereum ٹرانسفرز کے ارد گرد کامیابی کے چیکس کا اضافہ کرتی ہے تاکہ ریورٹ (revert) اور false ویلیو کی واپسی کو ایک ہی طرح سے ٹریٹ کیا جا سکے۔

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3pragma solidity >=0.6.0;
4
5// ERC20 ٹوکنز کے ساتھ تعامل کرنے اور ETH بھیجنے کے لیے ہیلپر میتھڈز جو مستقل طور پر true/false واپس نہیں کرتے
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
سب دکھائیں

ہم کسی دوسرے کنٹریکٹ کو دو میں سے کسی ایک طریقے سے کال کر سکتے ہیں:

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

ERC-20 معیار سے پہلے بنائے گئے ٹوکنز کے ساتھ بیک ورڈ کمپیٹیبلٹی (backwards compatibility) کی خاطر، ایک ERC-20 کال یا تو ریورٹ ہو کر فیل ہو سکتی ہے (جس صورت میں success کی ویلیو false ہوتی ہے) یا کامیاب ہو کر false ویلیو واپس کر سکتی ہے (جس صورت میں آؤٹ پٹ ڈیٹا موجود ہوتا ہے، اور اگر آپ اسے بولین (boolean) کے طور پر ڈیکوڈ کریں تو آپ کو 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 کی ٹرانسفر فعالیت (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) کو نافذ کرتا ہے، جو ایک اکاؤنٹ کو کسی دوسرے اکاؤنٹ کی طرف سے فراہم کردہ الاؤنس خرچ کرنے کی اجازت دیتا ہے۔

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) ٹرانسفر کرتا ہے۔ کسی دوسرے کنٹریکٹ پر کی جانے والی کوئی بھی کال ایتھر بھیجنے کی کوشش کر سکتی ہے۔ چونکہ ہمیں دراصل کسی فنکشن کو کال کرنے کی ضرورت نہیں ہے، اس لیے ہم کال کے ساتھ کوئی ڈیٹا نہیں بھیجتے۔

نتیجہ

یہ تقریباً ۵۰ صفحات کا ایک طویل مضمون ہے۔ اگر آپ یہاں تک پہنچ گئے ہیں، تو مبارک ہو! امید ہے کہ اب تک آپ ایک حقیقی ایپلی کیشن (مختصر نمونہ پروگراموں کے برعکس) لکھنے کے حوالے سے غور طلب باتوں کو سمجھ چکے ہوں گے اور اپنے استعمال کے معاملات کے لیے کانٹریکٹس لکھنے کے زیادہ قابل ہو گئے ہوں گے۔

اب جائیں اور کچھ مفید لکھیں اور ہمیں حیران کر دیں۔

میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔

صفحہ کی آخری اپ ڈیٹ: 25 فروری، 2026

کیا یہ ٹیوٹوریل مددگار تھا؟