యూనిస్వాప్-v2 కాంట్రాక్ట్ వాక్-త్రూ
పరిచయం
యూనిస్వాప్ v2 (opens in a new tab) ఏవైనా రెండు ERC-20 టోకెన్ల మధ్య మార్పిడి మార్కెట్ను సృష్టించగలదు. ఈ వ్యాసంలో, ఈ ప్రోటోకాల్ను అమలు చేసే కాంట్రాక్ట్ల సోర్స్ కోడ్ను పరిశీలిస్తాము మరియు అవి ఆ విధంగా ఎందుకు వ్రాయబడ్డాయో చూస్తాము.
యూనిస్వాప్ ఏమి చేస్తుంది?
ప్రాథమికంగా, రెండు రకాల వినియోగదారులు ఉంటారు: ద్రవ్యత సమకూర్చేవారు మరియు ట్రేడర్లు.
ద్రవ్యత సమకూర్చేవారు మార్పిడి చేయగల రెండు టోకెన్లను పూల్కు అందిస్తారు (వాటిని మనం Token0 మరియు Token1 అని పిలుస్తాము). దీనికి ప్రతిఫలంగా, వారు పూల్లో పాక్షిక యాజమాన్యాన్ని సూచించే మూడవ టోకెన్ను పొందుతారు, దీనిని ద్రవ్యత టోకెన్ అని పిలుస్తారు.
ట్రేడర్లు ఒక రకమైన టోకెన్ను పూల్కు పంపి, ద్రవ్యత సమకూర్చేవారు అందించిన పూల్ నుండి మరొక దానిని పొందుతారు (ఉదాహరణకు, Token0 పంపి Token1 పొందుతారు). పూల్లో ఉన్న Token0లు మరియు Token1ల సాపేక్ష సంఖ్య ఆధారంగా మార్పిడి రేటు నిర్ణయించబడుతుంది. అదనంగా, లిక్విడిటీ పూల్ కోసం పూల్ ఒక చిన్న శాతాన్ని ప్రతిఫలంగా తీసుకుంటుంది.
ద్రవ్యత సమకూర్చేవారు తమ ఆస్తులను తిరిగి పొందాలనుకున్నప్పుడు, వారు పూల్ టోకెన్లను దహనం చేసి, ప్రతిఫలాలలో తమ వాటాతో సహా తమ టోకెన్లను తిరిగి పొందవచ్చు.
పూర్తి వివరణ కోసం ఇక్కడ క్లిక్ చేయండి (opens in a new tab).
v2 ఎందుకు? v3 ఎందుకు కాదు?
యూనిస్వాప్ v3 (opens in a new tab) అనేది v2 కంటే చాలా సంక్లిష్టమైన అప్గ్రేడ్. ముందుగా v2 నేర్చుకుని, ఆపై v3కి వెళ్లడం సులభం.
కోర్ కాంట్రాక్ట్లు వర్సెస్ పెరిఫెరీ కాంట్రాక్ట్లు
యూనిస్వాప్ v2 రెండు భాగాలుగా విభజించబడింది, కోర్ మరియు పెరిఫెరీ. ఈ విభజన ఆస్తులను కలిగి ఉండి, అందువల్ల సురక్షితంగా ఉండాల్సిన కోర్ కాంట్రాక్ట్లను సరళంగా మరియు ఆడిట్ చేయడానికి సులభంగా ఉండేలా చేస్తుంది. ట్రేడర్లకు అవసరమైన అదనపు కార్యాచరణ అంతా పెరిఫెరీ కాంట్రాక్ట్ల ద్వారా అందించబడుతుంది.
డేటా మరియు నియంత్రణ ప్రవాహాలు
మీరు యూనిస్వాప్ యొక్క మూడు ప్రధాన చర్యలను చేసినప్పుడు జరిగే డేటా మరియు నియంత్రణ ప్రవాహం ఇది:
- వివిధ టోకెన్ల మధ్య మార్పిడి
- మార్కెట్కు ద్రవ్యతను జోడించి, జత మార్పిడి (pair exchange) ERC-20 ద్రవ్యత టోకెన్లతో ప్రతిఫలం పొందడం
- ERC-20 ద్రవ్యత టోకెన్లను దహనం చేసి, జత మార్పిడి ద్వారా ట్రేడర్లు మార్పిడి చేసుకోవడానికి అనుమతించే ERC-20 టోకెన్లను తిరిగి పొందడం
మార్పిడి
ఇది ట్రేడర్లు ఉపయోగించే అత్యంత సాధారణ ప్రవాహం:
కాలర్ (Caller)
- మార్పిడి చేయాల్సిన మొత్తంలో పెరిఫెరీ (periphery) ఖాతాకు అనుమతి మొత్తం అందించండి.
- పెరిఫెరీ కాంట్రాక్ట్ యొక్క అనేక మార్పిడి ఫంక్షన్లలో ఒకదాన్ని కాల్ చేయండి (ఏది అనేది ETH ప్రమేయం ఉందా లేదా, ట్రేడర్ డిపాజిట్ చేయాల్సిన టోకెన్ల మొత్తాన్ని లేదా తిరిగి పొందాల్సిన టోకెన్ల మొత్తాన్ని పేర్కొంటారా అనే దానిపై ఆధారపడి ఉంటుంది).
ప్రతి మార్పిడి ఫంక్షన్
pathను అంగీకరిస్తుంది, ఇది వెళ్లాల్సిన ఎక్స్ఛేంజీల శ్రేణి (array).
పెరిఫెరీ కాంట్రాక్ట్లో (UniswapV2Router02.sol)
- మార్గంలో ఉన్న ప్రతి ఎక్స్ఛేంజ్లో ట్రేడ్ చేయాల్సిన మొత్తాలను గుర్తించండి.
- మార్గం గుండా పునరావృతం (Iterate) చేస్తుంది. దారిలో ఉన్న ప్రతి ఎక్స్ఛేంజ్ కోసం ఇది ఇన్పుట్ టోకెన్ను పంపుతుంది మరియు ఆ తర్వాత ఎక్స్ఛేంజ్ యొక్క
swapఫంక్షన్ను కాల్ చేస్తుంది. చాలా సందర్భాలలో టోకెన్ల గమ్యస్థాన చిరునామా మార్గంలో ఉన్న తదుపరి జత మార్పిడి అవుతుంది. చివరి ఎక్స్ఛేంజ్లో ఇది ట్రేడర్ అందించిన చిరునామా అవుతుంది.
కోర్ కాంట్రాక్ట్లో (UniswapV2Pair.sol)
- కోర్ కాంట్రాక్ట్ మోసపోవడం లేదని మరియు మార్పిడి తర్వాత తగినంత ద్రవ్యతను నిర్వహించగలదని ధృవీకరించండి.
- తెలిసిన నిల్వలకు అదనంగా మన వద్ద ఎన్ని అదనపు టోకెన్లు ఉన్నాయో చూడండి. ఆ మొత్తమే మనం మార్పిడి చేయడానికి స్వీకరించిన ఇన్పుట్ టోకెన్ల సంఖ్య.
- అవుట్పుట్ టోకెన్లను గమ్యస్థానానికి పంపండి.
- రిజర్వ్ మొత్తాలను అప్డేట్ చేయడానికి
_updateను కాల్ చేయండి
తిరిగి పెరిఫెరీ కాంట్రాక్ట్లో (UniswapV2Router02.sol)
- అవసరమైన ఏదైనా క్లీనప్ చేయండి (ఉదాహరణకు, ట్రేడర్కు పంపడానికి ETHని తిరిగి పొందడానికి WETH టోకెన్లను దహనం చేయండి)
ద్రవ్యతను జోడించడం
కాలర్ (Caller)
- లిక్విడిటీ పూల్కు జోడించాల్సిన మొత్తాలలో పెరిఫెరీ ఖాతాకు అనుమతి మొత్తం అందించండి.
- పెరిఫెరీ కాంట్రాక్ట్ యొక్క
addLiquidityఫంక్షన్లలో ఒకదాన్ని కాల్ చేయండి.
పెరిఫెరీ కాంట్రాక్ట్లో (UniswapV2Router02.sol)
- అవసరమైతే కొత్త జత మార్పిడిని సృష్టించండి
- ఇప్పటికే ఉన్న జత మార్పిడి ఉంటే, జోడించాల్సిన టోకెన్ల మొత్తాన్ని లెక్కించండి. ఇది రెండు టోకెన్లకు ఒకే విలువను కలిగి ఉండాలి, కాబట్టి ఇప్పటికే ఉన్న టోకెన్లకు కొత్త టోకెన్ల నిష్పత్తి సమానంగా ఉంటుంది.
- మొత్తాలు ఆమోదయోగ్యమైనవో లేదో తనిఖీ చేయండి (కాలర్లు కనీస మొత్తాన్ని పేర్కొనవచ్చు, దాని కంటే తక్కువ ఉంటే వారు ద్రవ్యతను జోడించడానికి ఇష్టపడరు)
- కోర్ కాంట్రాక్ట్ను కాల్ చేయండి.
కోర్ కాంట్రాక్ట్లో (UniswapV2Pair.sol)
- ద్రవ్యత టోకెన్లను ముద్రించి, వాటిని కాలర్కు పంపండి
- రిజర్వ్ మొత్తాలను అప్డేట్ చేయడానికి
_updateను కాల్ చేయండి
ద్రవ్యతను తీసివేయడం
కాలర్ (Caller)
- అంతర్లీన టోకెన్లకు బదులుగా దహనం చేయడానికి ద్రవ్యత టోకెన్ల అనుమతి మొత్తాన్ని పెరిఫెరీ ఖాతాకు అందించండి.
- పెరిఫెరీ కాంట్రాక్ట్ యొక్క
removeLiquidityఫంక్షన్లలో ఒకదాన్ని కాల్ చేయండి.
పెరిఫెరీ కాంట్రాక్ట్లో (UniswapV2Router02.sol)
- ద్రవ్యత టోకెన్లను జత మార్పిడికి పంపండి
కోర్ కాంట్రాక్ట్లో (UniswapV2Pair.sol)
- దహనం చేసిన టోకెన్ల నిష్పత్తిలో అంతర్లీన టోకెన్లను గమ్యస్థాన చిరునామాకు పంపండి. ఉదాహరణకు పూల్లో 1000 A టోకెన్లు, 500 B టోకెన్లు మరియు 90 ద్రవ్యత టోకెన్లు ఉంటే, మరియు దహనం చేయడానికి మనం 9 టోకెన్లను స్వీకరిస్తే, మనం 10% ద్రవ్యత టోకెన్లను దహనం చేస్తున్నాము కాబట్టి వినియోగదారుకు 100 A టోకెన్లు మరియు 50 B టోకెన్లను తిరిగి పంపుతాము.
- ద్రవ్యత టోకెన్లను దహనం చేయండి
- రిజర్వ్ మొత్తాలను అప్డేట్ చేయడానికి
_updateను కాల్ చేయండి
కోర్ కాంట్రాక్ట్లు
ఇవి ద్రవ్యతను కలిగి ఉండే సురక్షితమైన కాంట్రాక్ట్లు.
UniswapV2Pair.sol
ఈ కాంట్రాక్ట్ (opens in a new tab) టోకెన్లను మార్పిడి చేసే అసలు పూల్ను అమలు చేస్తుంది. ఇది ప్రధాన యూనిస్వాప్ కార్యాచరణ.
pragma solidity =0.5.16;
import './interfaces/IUniswapV2Pair.sol';
import './UniswapV2ERC20.sol';
import './libraries/Math.sol';
import './libraries/UQ112x112.sol';
import './interfaces/IERC20.sol';
import './interfaces/IUniswapV2Factory.sol';
import './interfaces/IUniswapV2Callee.sol';
కాంట్రాక్ట్ వాటిని అమలు చేయడం వల్ల (IUniswapV2Pair మరియు UniswapV2ERC20) లేదా వాటిని అమలు చేసే కాంట్రాక్ట్లను కాల్ చేయడం వల్ల, కాంట్రాక్ట్ తెలుసుకోవలసిన ఇంటర్ఫేస్లు ఇవన్నీ.
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
ఈ కాంట్రాక్ట్ UniswapV2ERC20 నుండి వారసత్వంగా వస్తుంది, ఇది ద్రవ్యత టోకెన్ల కోసం ERC-20 ఫంక్షన్లను అందిస్తుంది.
using SafeMath for uint;
ఓవర్ఫ్లోలు మరియు అండర్ఫ్లోలను నివారించడానికి SafeMath లైబ్రరీ (opens in a new tab) ఉపయోగించబడుతుంది. ఇది చాలా ముఖ్యం ఎందుకంటే లేకపోతే మనం ఒక విలువ -1 ఉండాల్సిన చోట, దానికి బదులుగా 2^256-1 ఉండే పరిస్థితిని ఎదుర్కోవచ్చు.
using UQ112x112 for uint224;
పూల్ కాంట్రాక్ట్లోని చాలా లెక్కలకు భిన్నాలు అవసరం. అయితే, EVM ద్వారా భిన్నాలకు మద్దతు లేదు.
యూనిస్వాప్ కనుగొన్న పరిష్కారం ఏమిటంటే 224 బిట్ విలువలను ఉపయోగించడం, ఇందులో పూర్ణాంక భాగానికి 112 బిట్లు మరియు భిన్నానికి 112 బిట్లు ఉంటాయి. కాబట్టి 1.0 అనేది 2^112 గా సూచించబడుతుంది, 1.5 అనేది 2^112 + 2^111 గా సూచించబడుతుంది, మొదలైనవి.
ఈ లైబ్రరీ గురించి మరిన్ని వివరాలు ఈ డాక్యుమెంట్లో తర్వాత అందుబాటులో ఉన్నాయి.
వేరియబుల్స్
uint public constant MINIMUM_LIQUIDITY = 10**3;
సున్నాతో భాగించే సందర్భాలను నివారించడానికి, ఎల్లప్పుడూ ఉండే కనీస ద్రవ్యత టోకెన్ల సంఖ్య ఉంటుంది (కానీ అవి ఖాతా సున్నా స్వంతం). ఆ సంఖ్య MINIMUM_LIQUIDITY, వెయ్యి.
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
ఇది ERC-20 బదిలీ ఫంక్షన్ కోసం ABI సెలెక్టర్. రెండు టోకెన్ ఖాతాలలో ERC-20 టోకెన్లను బదిలీ చేయడానికి ఇది ఉపయోగించబడుతుంది.
address public factory;
ఈ పూల్ను సృష్టించిన ఫ్యాక్టరీ కాంట్రాక్ట్ ఇది. ప్రతి పూల్ అనేది రెండు ERC-20 టోకెన్ల మధ్య మార్పిడి, ఫ్యాక్టరీ అనేది ఈ పూల్స్ అన్నింటినీ కలిపే కేంద్ర బిందువు.
address public token0;
address public token1;
ఈ పూల్ ద్వారా మార్పిడి చేయగల రెండు రకాల ERC-20 టోకెన్ల కోసం కాంట్రాక్ట్ల చిరునామాలు ఇక్కడ ఉన్నాయి.
uint112 private reserve0; // ఒకే స్టోరేజ్ స్లాట్ను ఉపయోగిస్తుంది, getReserves ద్వారా యాక్సెస్ చేయవచ్చు
uint112 private reserve1; // ఒకే స్టోరేజ్ స్లాట్ను ఉపయోగిస్తుంది, getReserves ద్వారా యాక్సెస్ చేయవచ్చు
ప్రతి టోకెన్ రకానికి పూల్ కలిగి ఉన్న నిల్వలు. ఈ రెండూ ఒకే మొత్తంలో విలువను సూచిస్తాయని మేము భావిస్తున్నాము, కాబట్టి ప్రతి token0 విలువ reserve1/reserve0 token1 లకు సమానం.
uint32 private blockTimestampLast; // ఒకే స్టోరేజ్ స్లాట్ను ఉపయోగిస్తుంది, getReserves ద్వారా యాక్సెస్ చేయవచ్చు
మార్పిడి జరిగిన చివరి బ్లాక్ కోసం టైమ్స్టాంప్, కాలక్రమేణా మార్పిడి రేట్లను ట్రాక్ చేయడానికి ఉపయోగించబడుతుంది.
ఎథీరియం కాంట్రాక్ట్ల యొక్క అతిపెద్ద గ్యాస్ ఖర్చులలో ఒకటి నిల్వ, ఇది కాంట్రాక్ట్ యొక్క ఒక కాల్ నుండి తదుపరి కాల్ వరకు కొనసాగుతుంది. ప్రతి నిల్వ సెల్ 256 బిట్ల పొడవు ఉంటుంది. కాబట్టి మూడు వేరియబుల్స్, reserve0, reserve1, మరియు blockTimestampLast, ఒకే నిల్వ విలువలో మూడింటినీ చేర్చే విధంగా కేటాయించబడతాయి (112+112+32=256).
uint public price0CumulativeLast;
uint public price1CumulativeLast;
ఈ వేరియబుల్స్ ప్రతి టోకెన్ కోసం సంచిత ఖర్చులను కలిగి ఉంటాయి (ఒకదాని పరంగా మరొకటి). ఒక నిర్దిష్ట కాల వ్యవధిలో సగటు మార్పిడి రేటును లెక్కించడానికి వీటిని ఉపయోగించవచ్చు.
uint public kLast; // reserve0 * reserve1, అత్యంత ఇటీవలి ద్రవ్యత ఈవెంట్ జరిగిన వెంటనే ఉన్నట్లుగా
token0 మరియు token1 మధ్య మార్పిడి రేటును పెయిర్ ఎక్స్ఛేంజ్ నిర్ణయించే విధానం ఏమిటంటే, ట్రేడ్ల సమయంలో రెండు నిల్వల గుణకాన్ని స్థిరంగా ఉంచడం. kLast అనేది ఈ విలువ. ద్రవ్యత సమకూర్చేవారు టోకెన్లను డిపాజిట్ చేసినప్పుడు లేదా ఉపసంహరించుకున్నప్పుడు ఇది మారుతుంది మరియు 0.3% మార్కెట్ రుసుము కారణంగా ఇది కొద్దిగా పెరుగుతుంది.
ఇక్కడ ఒక సాధారణ ఉదాహరణ ఉంది. సరళత కోసం పట్టికలో దశాంశ బిందువు తర్వాత మూడు అంకెలు మాత్రమే ఉన్నాయని గమనించండి మరియు మేము 0.3% ట్రేడింగ్ రుసుమును విస్మరిస్తాము కాబట్టి సంఖ్యలు ఖచ్చితమైనవి కావు.
| ఈవెంట్ | reserve0 | reserve1 | reserve0 * reserve1 | సగటు మార్పిడి రేటు (token1 / token0) |
|---|---|---|---|---|
| ప్రారంభ సెటప్ | 1,000.000 | 1,000.000 | 1,000,000 | |
| ట్రేడర్ A 47.619 token1 కోసం 50 token0 ని మార్పిడి చేస్తారు | 1,050.000 | 952.381 | 1,000,000 | 0.952 |
| ట్రేడర్ B 8.984 token1 కోసం 10 token0 ని మార్పిడి చేస్తారు | 1,060.000 | 943.396 | 1,000,000 | 0.898 |
| ట్రేడర్ C 34.305 token1 కోసం 40 token0 ని మార్పిడి చేస్తారు | 1,100.000 | 909.090 | 1,000,000 | 0.858 |
| ట్రేడర్ D 109.01 token0 కోసం 100 token1 ని మార్పిడి చేస్తారు | 990.990 | 1,009.090 | 1,000,000 | 0.917 |
| ట్రేడర్ E 10.079 token1 కోసం 10 token0 ని మార్పిడి చేస్తారు | 1,000.990 | 999.010 | 1,000,000 | 1.008 |
ట్రేడర్లు token0 ని ఎక్కువగా అందించినప్పుడు, సరఫరా మరియు డిమాండ్ ఆధారంగా token1 యొక్క సాపేక్ష విలువ పెరుగుతుంది మరియు దీనికి విరుద్ధంగా కూడా జరుగుతుంది.
లాక్
uint private unlocked = 1;
రీఎంట్రెన్సీ దుర్వినియోగం (opens in a new tab) ఆధారంగా భద్రతా లోపాల తరగతి ఒకటి ఉంది. యూనిస్వాప్ ఏకపక్ష ERC-20 టోకెన్లను బదిలీ చేయాలి, అంటే వాటిని కాల్ చేసే యూనిస్వాప్ మార్కెట్ను దుర్వినియోగం చేయడానికి ప్రయత్నించే ERC-20 కాంట్రాక్ట్లను కాల్ చేయడం.
కాంట్రాక్ట్లో భాగంగా unlocked వేరియబుల్ను కలిగి ఉండటం ద్వారా, ఫంక్షన్లు రన్ అవుతున్నప్పుడు (అదే లావాదేవీలో) వాటిని కాల్ చేయకుండా నిరోధించవచ్చు.
modifier lock() {
ఈ ఫంక్షన్ ఒక మాడిఫైయర్ (opens in a new tab), ఇది సాధారణ ఫంక్షన్ చుట్టూ చుట్టి దాని ప్రవర్తనను ఏదో ఒక విధంగా మార్చే ఫంక్షన్.
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
unlocked ఒకటికి సమానంగా ఉంటే, దానిని సున్నాకి సెట్ చేయండి. ఇది ఇప్పటికే సున్నా అయితే కాల్ను రివర్ట్ చేయండి, దానిని విఫలమయ్యేలా చేయండి.
_;
మాడిఫైయర్లో _; అనేది అసలు ఫంక్షన్ కాల్ (అన్ని పారామితులతో). ఇక్కడ దీని అర్థం ఏమిటంటే, కాల్ చేసినప్పుడు unlocked ఒకటిగా ఉంటే మాత్రమే ఫంక్షన్ కాల్ జరుగుతుంది మరియు అది రన్ అవుతున్నప్పుడు unlocked విలువ సున్నాగా ఉంటుంది.
unlocked = 1;
}
ప్రధాన ఫంక్షన్ తిరిగి వచ్చిన తర్వాత, లాక్ను విడుదల చేయండి.
ఇతర ఫంక్షన్లు
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
ఈ ఫంక్షన్ కాలర్లకు ఎక్స్ఛేంజ్ యొక్క ప్రస్తుత స్థితిని అందిస్తుంది. Solidity ఫంక్షన్లు బహుళ విలువలను తిరిగి ఇవ్వగలవని (opens in a new tab) గమనించండి.
function _safeTransfer(address token, address to, uint value) private {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
ఈ అంతర్గత ఫంక్షన్ ఎక్స్ఛేంజ్ నుండి వేరొకరికి కొంత మొత్తంలో ERC20 టోకెన్లను బదిలీ చేస్తుంది. మనం కాల్ చేస్తున్న ఫంక్షన్ transfer(address,uint) అని SELECTOR నిర్దేశిస్తుంది (పైన ఉన్న నిర్వచనాన్ని చూడండి).
టోకెన్ ఫంక్షన్ కోసం ఇంటర్ఫేస్ను దిగుమతి చేయడాన్ని నివారించడానికి, మేము ABI ఫంక్షన్లలో (opens in a new tab) ఒకదాన్ని ఉపయోగించి "మాన్యువల్గా" కాల్ను సృష్టిస్తాము.
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
ERC-20 బదిలీ కాల్ వైఫల్యాన్ని నివేదించడానికి రెండు మార్గాలు ఉన్నాయి:
- రివర్ట్. బాహ్య కాంట్రాక్ట్కు కాల్ రివర్ట్ అయితే, బూలియన్ రిటర్న్ విలువ
falseఅవుతుంది - సాధారణంగా ముగుస్తుంది కానీ వైఫల్యాన్ని నివేదిస్తుంది. ఆ సందర్భంలో రిటర్న్ వాల్యూ బఫర్ సున్నా కాని పొడవును కలిగి ఉంటుంది మరియు బూలియన్ విలువగా డీకోడ్ చేసినప్పుడు అది
falseఅవుతుంది
ఈ షరతులలో ఏది జరిగినా, రివర్ట్ చేయండి.
ఈవెంట్లు
event Mint(address indexed sender, uint amount0, uint amount1);
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
ద్రవ్యత సమకూర్చేవారు ద్రవ్యతను డిపాజిట్ చేసినప్పుడు (Mint) లేదా దానిని ఉపసంహరించుకున్నప్పుడు (Burn) ఈ రెండు ఈవెంట్లు వెలువడతాయి. ఏ సందర్భంలోనైనా, డిపాజిట్ చేయబడిన లేదా ఉపసంహరించబడిన token0 మరియు token1 మొత్తాలు ఈవెంట్లో భాగంగా ఉంటాయి, అలాగే మమ్మల్ని కాల్ చేసిన ఖాతా యొక్క గుర్తింపు (sender) కూడా ఉంటుంది. ఉపసంహరణ విషయంలో, ఈవెంట్లో టోకెన్లను స్వీకరించిన లక్ష్యం (to) కూడా ఉంటుంది, ఇది పంపినవారికి సమానంగా ఉండకపోవచ్చు.
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
ఒక ట్రేడర్ ఒక టోకెన్ను మరొకదానితో మార్పిడి చేసినప్పుడు ఈ ఈవెంట్ వెలువడుతుంది. మళ్ళీ, పంపినవారు మరియు గమ్యస్థానం ఒకేలా ఉండకపోవచ్చు. ప్రతి టోకెన్ ఎక్స్ఛేంజ్కు పంపబడవచ్చు లేదా దాని నుండి స్వీకరించబడవచ్చు.
event Sync(uint112 reserve0, uint112 reserve1);
చివరగా, కారణంతో సంబంధం లేకుండా టోకెన్లు జోడించబడిన లేదా ఉపసంహరించబడిన ప్రతిసారీ తాజా నిల్వ సమాచారాన్ని (మరియు తద్వారా మార్పిడి రేటును) అందించడానికి Sync వెలువడుతుంది.
సెటప్ ఫంక్షన్లు
కొత్త పెయిర్ ఎక్స్ఛేంజ్ సెటప్ చేయబడినప్పుడు ఈ ఫంక్షన్లను ఒకసారి కాల్ చేయాలి.
constructor() public {
factory = msg.sender;
}
పెయిర్ను సృష్టించిన ఫ్యాక్టరీ చిరునామాను మేము ట్రాక్ చేస్తామని కన్స్ట్రక్టర్ నిర్ధారిస్తుంది. ఈ సమాచారం initialize కోసం మరియు ఫ్యాక్టరీ రుసుము కోసం (ఒకవేళ ఉంటే) అవసరం
// డిప్లాయ్మెంట్ సమయంలో ఫ్యాక్టరీ ద్వారా ఒకసారి పిలువబడుతుంది
function initialize(address _token0, address _token1) external {
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // తగినంత తనిఖీ
token0 = _token0;
token1 = _token1;
}
ఈ ఫంక్షన్ ఫ్యాక్టరీని (మరియు ఫ్యాక్టరీని మాత్రమే) ఈ పెయిర్ మార్పిడి చేసే రెండు ERC-20 టోకెన్లను పేర్కొనడానికి అనుమతిస్తుంది.
అంతర్గత అప్డేట్ ఫంక్షన్లు
_update
// రిజర్వ్లను అప్డేట్ చేయండి మరియు ప్రతి బ్లాక్కు మొదటి కాల్లో, ధర అక్యుమ్యులేటర్లను అప్డేట్ చేయండి
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
టోకెన్లు డిపాజిట్ చేయబడిన లేదా ఉపసంహరించబడిన ప్రతిసారీ ఈ ఫంక్షన్ కాల్ చేయబడుతుంది.
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
balance0 లేదా balance1 (uint256) uint112(-1) (=2^112-1) కంటే ఎక్కువగా ఉంటే (కాబట్టి ఇది uint112 కి మార్చబడినప్పుడు ఓవర్ఫ్లో అవుతుంది & తిరిగి 0 కి వస్తుంది) ఓవర్ఫ్లోలను నివారించడానికి _update ని కొనసాగించడానికి నిరాకరించండి. 10^18 యూనిట్లుగా విభజించగల సాధారణ టోకెన్తో, దీని అర్థం ప్రతి మార్పిడి ప్రతి టోకెన్లలో సుమారు 5.1*10^15 కి పరిమితం చేయబడింది. ఇప్పటివరకు అది సమస్య కాలేదు.
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // ఓవర్ఫ్లో కావాలి
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
గడిచిన సమయం సున్నా కాకపోతే, ఈ బ్లాక్లో మనం మొదటి మార్పిడి లావాదేవీ అని అర్థం. ఆ సందర్భంలో, మనం కాస్ట్ అక్యుమ్యులేటర్లను అప్డేట్ చేయాలి.
// * ఎప్పుడూ ఓవర్ఫ్లో కాదు, మరియు + ఓవర్ఫ్లో కావాలి
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
ప్రతి కాస్ట్ అక్యుమ్యులేటర్ తాజా ఖర్చు (ఇతర టోకెన్ నిల్వ/ఈ టోకెన్ నిల్వ) మరియు గడిచిన సమయాన్ని సెకన్లలో గుణించడం ద్వారా అప్డేట్ చేయబడుతుంది. సగటు ధరను పొందడానికి, మీరు రెండు సమయ బిందువులలో సంచిత ధరను చదివి, వాటి మధ్య ఉన్న సమయ వ్యత్యాసంతో భాగిస్తారు. ఉదాహరణకు, ఈ ఈవెంట్ల క్రమాన్ని ఊహించండి:
| ఈవెంట్ | reserve0 | reserve1 | టైమ్స్టాంప్ | ఉపాంత మార్పిడి రేటు (reserve1 / reserve0) | price0CumulativeLast |
|---|---|---|---|---|---|
| ప్రారంభ సెటప్ | 1,000.000 | 1,000.000 | 5,000 | 1.000 | 0 |
| ట్రేడర్ A 50 token0 ని డిపాజిట్ చేసి 47.619 token1 ని తిరిగి పొందుతారు | 1,050.000 | 952.381 | 5,020 | 0.907 | 20 |
| ట్రేడర్ B 10 token0 ని డిపాజిట్ చేసి 8.984 token1 ని తిరిగి పొందుతారు | 1,060.000 | 943.396 | 5,030 | 0.890 | 20+10*0.907 = 29.07 |
| ట్రేడర్ C 40 token0 ని డిపాజిట్ చేసి 34.305 token1 ని తిరిగి పొందుతారు | 1,100.000 | 909.090 | 5,100 | 0.826 | 29.07+70*0.890 = 91.37 |
| ట్రేడర్ D 100 token1 ని డిపాజిట్ చేసి 109.01 token0 ని తిరిగి పొందుతారు | 990.990 | 1,009.090 | 5,110 | 1.018 | 91.37+10*0.826 = 99.63 |
| ట్రేడర్ E 10 token0 ని డిపాజిట్ చేసి 10.079 token1 ని తిరిగి పొందుతారు | 1,000.990 | 999.010 | 5,150 | 0.998 | 99.63+40*1.1018 = 143.702 |
టైమ్స్టాంప్లు 5,030 మరియు 5,150 మధ్య Token0 సగటు ధరను లెక్కించాలనుకుంటున్నాము అనుకుందాం. price0Cumulative విలువలో వ్యత్యాసం 143.702-29.07=114.632. ఇది రెండు నిమిషాల (120 సెకన్లు) సగటు. కాబట్టి సగటు ధర 114.632/120 = 0.955.
ఈ ధర గణన కారణంగానే మనం పాత నిల్వ పరిమాణాలను తెలుసుకోవాలి.
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
emit Sync(reserve0, reserve1);
}
చివరగా, గ్లోబల్ వేరియబుల్స్ను అప్డేట్ చేయండి మరియు Sync ఈవెంట్ను వెలువరించండి.
_mintFee
// ఫీజు ఆన్లో ఉంటే, sqrt(k) వృద్ధిలో 1/6వ వంతుకు సమానమైన ద్రవ్యతను ముద్రించండి
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
యూనిస్వాప్ 2.0లో ట్రేడర్లు మార్కెట్ను ఉపయోగించడానికి 0.30% రుసుము చెల్లిస్తారు. ఆ రుసుములో ఎక్కువ భాగం (ట్రేడ్లో 0.25%) ఎల్లప్పుడూ ద్రవ్యత సమకూర్చేవారికి వెళుతుంది. మిగిలిన 0.05% ద్రవ్యత సమకూర్చేవారికి లేదా ఫ్యాక్టరీ ద్వారా ప్రోటోకాల్ రుసుముగా పేర్కొనబడిన చిరునామాకు వెళ్లవచ్చు, ఇది యూనిస్వాప్ వారి అభివృద్ధి ప్రయత్నానికి చెల్లిస్తుంది.
గణనలను (మరియు తద్వారా గ్యాస్ ఖర్చులను) తగ్గించడానికి, ఈ రుసుము ప్రతి లావాదేవీ వద్ద కాకుండా, పూల్కు ద్రవ్యత జోడించబడినప్పుడు లేదా తీసివేయబడినప్పుడు మాత్రమే లెక్కించబడుతుంది.
address feeTo = IUniswapV2Factory(factory).feeTo();
feeOn = feeTo != address(0);
ఫ్యాక్టరీ యొక్క రుసుము గమ్యాన్ని చదవండి. అది సున్నా అయితే ప్రోటోకాల్ రుసుము ఉండదు మరియు ఆ రుసుమును లెక్కించాల్సిన అవసరం లేదు.
uint _kLast = kLast; // గ్యాస్ ఆదా
kLast స్థితి వేరియబుల్ నిల్వలో ఉంది, కాబట్టి ఇది కాంట్రాక్ట్కు వేర్వేరు కాల్ల మధ్య విలువను కలిగి ఉంటుంది.
కాంట్రాక్ట్కు ఫంక్షన్ కాల్ ముగిసినప్పుడు విడుదలయ్యే అస్థిర మెమరీకి యాక్సెస్ కంటే నిల్వకు యాక్సెస్ చాలా ఖరీదైనది, కాబట్టి గ్యాస్ను ఆదా చేయడానికి మేము అంతర్గత వేరియబుల్ను ఉపయోగిస్తాము.
if (feeOn) {
if (_kLast != 0) {
ద్రవ్యత సమకూర్చేవారు వారి ద్రవ్యత టోకెన్ల ప్రశంసల ద్వారా వారి వాటాను పొందుతారు. కానీ ప్రోటోకాల్ రుసుముకు కొత్త ద్రవ్యత టోకెన్లను ముద్రించి feeTo చిరునామాకు అందించడం అవసరం.
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
uint rootKLast = Math.sqrt(_kLast);
if (rootK > rootKLast) {
ప్రోటోకాల్ రుసుమును వసూలు చేయడానికి కొత్త ద్రవ్యత ఉంటే. మీరు స్క్వేర్ రూట్ ఫంక్షన్ను ఈ ఆర్టికల్లో తర్వాత చూడవచ్చు
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
uint denominator = rootK.mul(5).add(rootKLast);
uint liquidity = numerator / denominator;
రుసుముల యొక్క ఈ సంక్లిష్టమైన గణన శ్వేతపత్రం (opens in a new tab) లోని 5వ పేజీలో వివరించబడింది. kLast లెక్కించబడిన సమయం మరియు ప్రస్తుత సమయం మధ్య ఎటువంటి ద్రవ్యత జోడించబడలేదని లేదా తీసివేయబడలేదని మాకు తెలుసు (ఎందుకంటే ద్రవ్యత జోడించబడిన లేదా తీసివేయబడిన ప్రతిసారీ, అది వాస్తవానికి మారడానికి ముందే మేము ఈ గణనను అమలు చేస్తాము), కాబట్టి reserve0 * reserve1 లో ఏదైనా మార్పు లావాదేవీ రుసుముల నుండి రావాలి (అవి లేకుండా మేము reserve0 * reserve1 ని స్థిరంగా ఉంచుతాము).
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
అదనపు ద్రవ్యత టోకెన్లను వాస్తవంగా సృష్టించడానికి మరియు వాటిని feeTo కి కేటాయించడానికి UniswapV2ERC20._mint ఫంక్షన్ను ఉపయోగించండి.
} else if (_kLast != 0) {
kLast = 0;
}
}
రుసుము లేకపోతే kLast ని సున్నాకి సెట్ చేయండి (అది ఇప్పటికే అలా లేకపోతే). ఈ కాంట్రాక్ట్ వ్రాయబడినప్పుడు గ్యాస్ రీఫండ్ ఫీచర్ (opens in a new tab) ఉండేది, ఇది కాంట్రాక్ట్లకు అవసరం లేని నిల్వను సున్నా చేయడం ద్వారా ఎథీరియం స్థితి యొక్క మొత్తం పరిమాణాన్ని తగ్గించడానికి ప్రోత్సహించింది.
సాధ్యమైనప్పుడు ఈ కోడ్ ఆ రీఫండ్ను పొందుతుంది.
బాహ్యంగా యాక్సెస్ చేయగల ఫంక్షన్లు
ఏదైనా లావాదేవీ లేదా కాంట్రాక్ట్ ఈ ఫంక్షన్లను కాల్ చేయగలిగినప్పటికీ, అవి పెరిఫెరీ కాంట్రాక్ట్ నుండి కాల్ చేయబడేలా రూపొందించబడ్డాయని గమనించండి. మీరు వాటిని నేరుగా కాల్ చేస్తే మీరు పెయిర్ ఎక్స్ఛేంజ్ను మోసం చేయలేరు, కానీ మీరు పొరపాటు ద్వారా విలువను కోల్పోవచ్చు.
mint
// ముఖ్యమైన భద్రతా తనిఖీలను నిర్వహించే కాంట్రాక్ట్ నుండి ఈ లో-లెవల్ ఫంక్షన్ను పిలవాలి
function mint(address to) external lock returns (uint liquidity) {
ద్రవ్యత సమకూర్చేవారు పూల్కు ద్రవ్యతను జోడించినప్పుడు ఈ ఫంక్షన్ కాల్ చేయబడుతుంది. ఇది ప్రతిఫలంగా అదనపు ద్రవ్యత టోకెన్లను ముద్రిస్తుంది. అదే లావాదేవీలో ద్రవ్యతను జోడించిన తర్వాత దానిని కాల్ చేసే పెరిఫెరీ కాంట్రాక్ట్ నుండి దీనిని కాల్ చేయాలి (కాబట్టి చట్టబద్ధమైన యజమాని కంటే ముందు కొత్త ద్రవ్యతను క్లెయిమ్ చేసే లావాదేవీని మరెవరూ సమర్పించలేరు).
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // గ్యాస్ ఆదా
బహుళ విలువలను తిరిగి ఇచ్చే Solidity ఫంక్షన్ యొక్క ఫలితాలను చదివే విధానం ఇది. మేము చివరిగా తిరిగి వచ్చిన విలువలను, బ్లాక్ టైమ్స్టాంప్ను విస్మరిస్తాము, ఎందుకంటే అది మాకు అవసరం లేదు.
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
ప్రస్తుత బ్యాలెన్స్లను పొందండి మరియు ప్రతి టోకెన్ రకంలో ఎంత జోడించబడిందో చూడండి.
bool feeOn = _mintFee(_reserve0, _reserve1);
సేకరించాల్సిన ప్రోటోకాల్ రుసుములను (ఏవైనా ఉంటే) లెక్కించండి మరియు తదనుగుణంగా ద్రవ్యత టోకెన్లను ముద్రించండి. _mintFee కి పారామితులు పాత నిల్వ విలువలు కాబట్టి, రుసుముల కారణంగా పూల్ మార్పుల ఆధారంగా మాత్రమే రుసుము ఖచ్చితంగా లెక్కించబడుతుంది.
uint _totalSupply = totalSupply; // గ్యాస్ ఆదా, _mintFee లో totalSupply అప్డేట్ కావచ్చు కాబట్టి ఇక్కడే నిర్వచించాలి
if (_totalSupply == 0) {
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
_mint(address(0), MINIMUM_LIQUIDITY); // మొదటి MINIMUM_LIQUIDITY టోకెన్లను శాశ్వతంగా లాక్ చేయండి
ఇది మొదటి డిపాజిట్ అయితే, MINIMUM_LIQUIDITY టోకెన్లను సృష్టించండి మరియు వాటిని లాక్ చేయడానికి సున్నా చిరునామాకు పంపండి. వాటిని ఎప్పటికీ రీడీమ్ చేయలేము, అంటే పూల్ ఎప్పటికీ పూర్తిగా ఖాళీ చేయబడదు (ఇది కొన్ని చోట్ల సున్నాతో భాగించడం నుండి మనల్ని రక్షిస్తుంది). MINIMUM_LIQUIDITY విలువ వెయ్యి, చాలా ERC-20 లు టోకెన్లో 10^-18 వ యూనిట్లుగా విభజించబడ్డాయని పరిగణనలోకి తీసుకుంటే, ETH Wei గా విభజించబడినట్లుగా, ఇది ఒకే టోకెన్ విలువకు 10^-15 అవుతుంది. ఇది ఎక్కువ ఖర్చు కాదు.
మొదటి డిపాజిట్ సమయంలో రెండు టోకెన్ల సాపేక్ష విలువ మాకు తెలియదు, కాబట్టి డిపాజిట్ రెండు టోకెన్లలో సమాన విలువను అందిస్తుందని భావించి, మేము మొత్తాలను గుణించి స్క్వేర్ రూట్ తీసుకుంటాము.
ఆర్బిట్రేజ్కు విలువను కోల్పోకుండా ఉండటానికి, సమాన విలువను అందించడం డిపాజిటర్ ప్రయోజనాలకు అనుగుణంగా ఉంటుంది కాబట్టి మేము దీనిని విశ్వసించవచ్చు. రెండు టోకెన్ల విలువ ఒకేలా ఉందని అనుకుందాం, కానీ మా డిపాజిటర్ Token0 కంటే నాలుగు రెట్లు ఎక్కువ Token1 ని డిపాజిట్ చేశారు. పెయిర్ ఎక్స్ఛేంజ్ Token0 ని మరింత విలువైనదిగా భావిస్తుందనే వాస్తవాన్ని ఉపయోగించి ఒక ట్రేడర్ దాని నుండి విలువను సంగ్రహించవచ్చు.
| ఈవెంట్ | reserve0 | reserve1 | reserve0 * reserve1 | పూల్ విలువ (reserve0 + reserve1) |
|---|---|---|---|---|
| ప్రారంభ సెటప్ | 8 | 32 | 256 | 40 |
| ట్రేడర్ 8 Token0 టోకెన్లను డిపాజిట్ చేసి, 16 Token1 ని తిరిగి పొందుతారు | 16 | 16 | 256 | 32 |
మీరు చూడగలిగినట్లుగా, ట్రేడర్ అదనంగా 8 టోకెన్లను సంపాదించారు, ఇవి పూల్ విలువ తగ్గడం వల్ల వస్తాయి, ఇది దాని స్వంతదారు అయిన డిపాజిటర్ను దెబ్బతీస్తుంది.
} else {
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
తదుపరి ప్రతి డిపాజిట్తో రెండు ఆస్తుల మధ్య మార్పిడి రేటు మాకు ఇప్పటికే తెలుసు, మరియు ద్రవ్యత సమకూర్చేవారు రెండింటిలోనూ సమాన విలువను అందిస్తారని మేము ఆశిస్తున్నాము. వారు అలా చేయకపోతే, శిక్షగా వారు అందించిన తక్కువ విలువ ఆధారంగా మేము వారికి ద్రవ్యత టోకెన్లను ఇస్తాము.
ఇది ప్రారంభ డిపాజిట్ అయినా లేదా తదుపరి డిపాజిట్ అయినా, మేము అందించే ద్రవ్యత టోకెన్ల సంఖ్య reserve0*reserve1 లోని మార్పు యొక్క స్క్వేర్ రూట్కు సమానంగా ఉంటుంది మరియు ద్రవ్యత టోకెన్ విలువ మారదు (రెండు రకాల సమాన విలువలు లేని డిపాజిట్ను మేము పొందితే తప్ప, ఆ సందర్భంలో "జరిమానా" పంపిణీ చేయబడుతుంది). ఒకే విలువ కలిగిన రెండు టోకెన్లతో, మూడు మంచి డిపాజిట్లు మరియు ఒక చెడ్డ డిపాజిట్తో (ఒక టోకెన్ రకం మాత్రమే డిపాజిట్ చేయబడింది, కాబట్టి ఇది ఎటువంటి ద్రవ్యత టోకెన్లను ఉత్పత్తి చేయదు) ఇక్కడ మరొక ఉదాహరణ ఉంది.
| ఈవెంట్ | reserve0 | reserve1 | reserve0 * reserve1 | పూల్ విలువ (reserve0 + reserve1) | ఈ డిపాజిట్ కోసం ముద్రించబడిన ద్రవ్యత టోకెన్లు | మొత్తం ద్రవ్యత టోకెన్లు | ప్రతి ద్రవ్యత టోకెన్ విలువ |
|---|---|---|---|---|---|---|---|
| ప్రారంభ సెటప్ | 8.000 | 8.000 | 64 | 16.000 | 8 | 8 | 2.000 |
| ప్రతి రకంలో నాలుగు డిపాజిట్ చేయండి | 12.000 | 12.000 | 144 | 24.000 | 4 | 12 | 2.000 |
| ప్రతి రకంలో రెండు డిపాజిట్ చేయండి | 14.000 | 14.000 | 196 | 28.000 | 2 | 14 | 2.000 |
| అసమాన విలువ డిపాజిట్ | 18.000 | 14.000 | 252 | 32.000 | 0 | 14 | ~2.286 |
| ఆర్బిట్రేజ్ తర్వాత | ~15.874 | ~15.874 | 252 | ~31.748 | 0 | 14 | ~2.267 |
}
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
_mint(to, liquidity);
అదనపు ద్రవ్యత టోకెన్లను వాస్తవంగా సృష్టించడానికి మరియు వాటిని సరైన ఖాతాకు ఇవ్వడానికి UniswapV2ERC20._mint ఫంక్షన్ను ఉపయోగించండి.
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 మరియు reserve1 అప్డేట్ చేయబడ్డాయి
emit Mint(msg.sender, amount0, amount1);
}
స్థితి వేరియబుల్స్ను (reserve0, reserve1, మరియు అవసరమైతే kLast) అప్డేట్ చేయండి మరియు తగిన ఈవెంట్ను వెలువరించండి.
burn
// ముఖ్యమైన భద్రతా తనిఖీలను నిర్వహించే కాంట్రాక్ట్ నుండి ఈ లో-లెవల్ ఫంక్షన్ను పిలవాలి
function burn(address to) external lock returns (uint amount0, uint amount1) {
ద్రవ్యత ఉపసంహరించబడినప్పుడు మరియు తగిన ద్రవ్యత టోకెన్లను దహనం చేయవలసి వచ్చినప్పుడు ఈ ఫంక్షన్ కాల్ చేయబడుతుంది. దీనిని పెరిఫెరీ ఖాతా నుండి కూడా కాల్ చేయాలి.
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // గ్యాస్ ఆదా
address _token0 = token0; // గ్యాస్ ఆదా
address _token1 = token1; // గ్యాస్ ఆదా
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
uint liquidity = balanceOf[address(this)];
కాల్కు ముందు దహనం చేయవలసిన ద్రవ్యతను పెరిఫెరీ కాంట్రాక్ట్ ఈ కాంట్రాక్ట్కు బదిలీ చేసింది. ఆ విధంగా ఎంత ద్రవ్యతను దహనం చేయాలో మాకు తెలుసు, మరియు అది దహనం చేయబడుతుందని మేము నిర్ధారించుకోవచ్చు.
bool feeOn = _mintFee(_reserve0, _reserve1);
uint _totalSupply = totalSupply; // గ్యాస్ ఆదా, _mintFee లో totalSupply అప్డేట్ కావచ్చు కాబట్టి ఇక్కడే నిర్వచించాలి
amount0 = liquidity.mul(balance0) / _totalSupply; // బ్యాలెన్స్లను ఉపయోగించడం వల్ల దామాషా పంపిణీ నిర్ధారించబడుతుంది
amount1 = liquidity.mul(balance1) / _totalSupply; // బ్యాలెన్స్లను ఉపయోగించడం వల్ల దామాషా పంపిణీ నిర్ధారించబడుతుంది
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
ద్రవ్యత సమకూర్చేవారు రెండు టోకెన్ల సమాన విలువను పొందుతారు. ఈ విధంగా మేము మార్పిడి రేటును మార్చము.
_burn(address(this), liquidity);
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
_update(balance0, balance1, _reserve0, _reserve1);
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 మరియు reserve1 అప్డేట్ చేయబడ్డాయి
emit Burn(msg.sender, amount0, amount1, to);
}
మిగిలిన burn ఫంక్షన్ పైన ఉన్న mint ఫంక్షన్ యొక్క ప్రతిబింబం.
swap
// ముఖ్యమైన భద్రతా తనిఖీలను నిర్వహించే కాంట్రాక్ట్ నుండి ఈ లో-లెవల్ ఫంక్షన్ను పిలవాలి
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
ఈ ఫంక్షన్ కూడా పెరిఫెరీ కాంట్రాక్ట్ నుండి కాల్ చేయబడాలి.
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // గ్యాస్ ఆదా
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // _token{0,1} కోసం స్కోప్, స్టాక్ టూ డీప్ ఎర్రర్లను నివారిస్తుంది
స్థానిక వేరియబుల్స్ మెమరీలో నిల్వ చేయబడతాయి లేదా అవి చాలా ఎక్కువగా లేకపోతే, నేరుగా స్టాక్లో నిల్వ చేయబడతాయి. మనం సంఖ్యను పరిమితం చేయగలిగితే, మనం స్టాక్ను ఉపయోగిస్తాము కాబట్టి తక్కువ గ్యాస్ను ఉపయోగిస్తాము. మరిన్ని వివరాల కోసం ఎల్లో పేపర్, అధికారిక ఎథీరియం స్పెసిఫికేషన్లు (opens in a new tab), పేజీ 26, సమీకరణం 298 చూడండి.
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // ఆశావాదంగా టోకెన్లను బదిలీ చేయండి
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // ఆశావాదంగా టోకెన్లను బదిలీ చేయండి
ఈ బదిలీ ఆశాజనకంగా ఉంటుంది, ఎందుకంటే అన్ని షరతులు నెరవేరాయని మేము నిర్ధారించుకోవడానికి ముందే మేము బదిలీ చేస్తాము. ఎథీరియంలో ఇది సరే ఎందుకంటే కాల్లో తర్వాత షరతులు నెరవేరకపోతే మేము దాని నుండి మరియు అది సృష్టించిన ఏవైనా మార్పుల నుండి రివర్ట్ అవుతాము.
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
అభ్యర్థిస్తే మార్పిడి గురించి రిసీవర్కు తెలియజేయండి.
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
ప్రస్తుత బ్యాలెన్స్లను పొందండి. మార్పిడి కోసం మమ్మల్ని కాల్ చేయడానికి ముందు పెరిఫెరీ కాంట్రాక్ట్ మాకు టోకెన్లను పంపుతుంది. ఇది మోసపోవడం లేదని తనిఖీ చేయడాన్ని కాంట్రాక్ట్కు సులభతరం చేస్తుంది, ఈ తనిఖీ కోర్ కాంట్రాక్ట్లో జరగాలి (ఎందుకంటే మా పెరిఫెరీ కాంట్రాక్ట్ కాకుండా ఇతర ఎంటిటీల ద్వారా మమ్మల్ని కాల్ చేయవచ్చు).
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // reserve{0,1}Adjusted కోసం స్కోప్, స్టాక్ టూ డీప్ ఎర్రర్లను నివారిస్తుంది
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
మార్పిడి నుండి మనం నష్టపోకుండా చూసుకోవడానికి ఇది ఒక సానిటీ చెక్. మార్పిడి reserve0*reserve1 ని తగ్గించే పరిస్థితి ఏదీ లేదు. మార్పిడిపై 0.3% రుసుము పంపబడుతుందని మేము నిర్ధారించే ప్రదేశం కూడా ఇదే; K విలువను సానిటీ చెక్ చేయడానికి ముందు, మేము రెండు బ్యాలెన్స్లను 1000 తో గుణించి, 3 తో గుణించిన మొత్తాలను తీసివేస్తాము, అంటే దాని K విలువను ప్రస్తుత నిల్వల K విలువతో పోల్చడానికి ముందు బ్యాలెన్స్ నుండి 0.3% (3/1000 = 0.003 = 0.3%) తీసివేయబడుతుంది.
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
reserve0 మరియు reserve1 ని అప్డేట్ చేయండి, మరియు అవసరమైతే ధర అక్యుమ్యులేటర్లు మరియు టైమ్స్టాంప్ను అప్డేట్ చేయండి మరియు ఈవెంట్ను వెలువరించండి.
సమకాలీకరణ లేదా స్కిమ్
పెయిర్ ఎక్స్ఛేంజ్ తన వద్ద ఉందని భావించే నిల్వలతో నిజమైన బ్యాలెన్స్లు సమకాలీకరణ కోల్పోయే అవకాశం ఉంది.
కాంట్రాక్ట్ సమ్మతి లేకుండా టోకెన్లను ఉపసంహరించుకోవడానికి మార్గం లేదు, కానీ డిపాజిట్లు వేరే విషయం. ఒక ఖాతా mint లేదా swap ని కాల్ చేయకుండా ఎక్స్ఛేంజ్కు టోకెన్లను బదిలీ చేయవచ్చు.
ఆ సందర్భంలో రెండు పరిష్కారాలు ఉన్నాయి:
sync, ప్రస్తుత బ్యాలెన్స్లకు నిల్వలను అప్డేట్ చేయండిskim, అదనపు మొత్తాన్ని ఉపసంహరించుకోండి. టోకెన్లను ఎవరు డిపాజిట్ చేశారో మాకు తెలియదు కాబట్టి ఏ ఖాతా అయినాskimని కాల్ చేయడానికి అనుమతించబడుతుందని గమనించండి. ఈ సమాచారం ఈవెంట్లో వెలువడుతుంది, కానీ ఈవెంట్లు బ్లాక్చైన్ నుండి యాక్సెస్ చేయబడవు.
// బ్యాలెన్స్లు రిజర్వ్లకు సరిపోయేలా బలవంతం చేయండి
function skim(address to) external lock {
address _token0 = token0; // గ్యాస్ ఆదా
address _token1 = token1; // గ్యాస్ ఆదా
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}
// రిజర్వ్లు బ్యాలెన్స్లకు సరిపోయేలా బలవంతం చేయండి
function sync() external lock {
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}
UniswapV2Factory.sol
ఈ కాంట్రాక్ట్ (opens in a new tab) పెయిర్ ఎక్స్ఛేంజ్లను సృష్టిస్తుంది.
pragma solidity =0.5.16;
import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';
contract UniswapV2Factory is IUniswapV2Factory {
address public feeTo;
address public feeToSetter;
ప్రోటోకాల్ రుసుమును అమలు చేయడానికి ఈ స్థితి వేరియబుల్స్ అవసరం (శ్వేతపత్రం (opens in a new tab), పేజీ 5 చూడండి).
feeTo చిరునామా ప్రోటోకాల్ రుసుము కోసం ద్రవ్యత టోకెన్లను కూడబెడుతుంది మరియు feeTo ని వేరే చిరునామాకు మార్చడానికి అనుమతించబడిన చిరునామా feeToSetter.
mapping(address => mapping(address => address)) public getPair;
address[] public allPairs;
ఈ వేరియబుల్స్ పెయిర్లను, రెండు టోకెన్ రకాల మధ్య మార్పిడిలను ట్రాక్ చేస్తాయి.
మొదటిది, getPair, ఇది మార్పిడి చేసే రెండు ERC-20 టోకెన్ల ఆధారంగా పెయిర్ ఎక్స్ఛేంజ్ కాంట్రాక్ట్ను గుర్తించే మ్యాపింగ్. ERC-20 టోకెన్లు వాటిని అమలు చేసే కాంట్రాక్ట్ల చిరునామాల ద్వారా గుర్తించబడతాయి, కాబట్టి కీలు మరియు విలువ అన్నీ చిరునామాలే. tokenA నుండి tokenB కి మార్చడానికి మిమ్మల్ని అనుమతించే పెయిర్ ఎక్స్ఛేంజ్ చిరునామాను పొందడానికి, మీరు getPair[<tokenA address>][<tokenB address>] ని ఉపయోగిస్తారు (లేదా దానికి విరుద్ధంగా).
రెండవ వేరియబుల్, allPairs, ఈ ఫ్యాక్టరీ ద్వారా సృష్టించబడిన పెయిర్ ఎక్స్ఛేంజ్ల యొక్క అన్ని చిరునామాలను కలిగి ఉన్న శ్రేణి. ఎథీరియంలో మీరు మ్యాపింగ్ కంటెంట్పై మళ్ళీ మళ్ళీ వెళ్ళలేరు లేదా అన్ని కీల జాబితాను పొందలేరు, కాబట్టి ఈ ఫ్యాక్టరీ ఏ ఎక్స్ఛేంజ్లను నిర్వహిస్తుందో తెలుసుకోవడానికి ఈ వేరియబుల్ మాత్రమే ఏకైక మార్గం.
గమనిక: మీరు మ్యాపింగ్ యొక్క అన్ని కీలపై మళ్ళీ మళ్ళీ వెళ్ళలేకపోవడానికి కారణం కాంట్రాక్ట్ డేటా నిల్వ ఖరీదైనది, కాబట్టి మనం దానిని ఎంత తక్కువగా ఉపయోగిస్తే అంత మంచిది మరియు మనం దానిని ఎంత తక్కువగా మార్చుకుంటే అంత మంచిది. మీరు పునరావృతానికి మద్దతు ఇచ్చే మ్యాపింగ్లను (opens in a new tab) సృష్టించవచ్చు, కానీ వాటికి కీల జాబితా కోసం అదనపు నిల్వ అవసరం. చాలా అప్లికేషన్లలో మీకు అది అవసరం లేదు.
event PairCreated(address indexed token0, address indexed token1, address pair, uint);
కొత్త పెయిర్ ఎక్స్ఛేంజ్ సృష్టించబడినప్పుడు ఈ ఈవెంట్ వెలువడుతుంది. ఇందులో టోకెన్ల చిరునామాలు, పెయిర్ ఎక్స్ఛేంజ్ చిరునామా మరియు ఫ్యాక్టరీ నిర్వహించే మొత్తం ఎక్స్ఛేంజ్ల సంఖ్య ఉంటాయి.
constructor(address _feeToSetter) public {
feeToSetter = _feeToSetter;
}
కన్స్ట్రక్టర్ చేసే ఏకైక పని feeToSetter ని పేర్కొనడం. ఫ్యాక్టరీలు రుసుము లేకుండా ప్రారంభమవుతాయి మరియు feeSetter మాత్రమే దానిని మార్చగలదు.
function allPairsLength() external view returns (uint) {
return allPairs.length;
}
ఈ ఫంక్షన్ ఎక్స్ఛేంజ్ పెయిర్ల సంఖ్యను తిరిగి ఇస్తుంది.
function createPair(address tokenA, address tokenB) external returns (address pair) {
రెండు ERC-20 టోకెన్ల మధ్య పెయిర్ ఎక్స్ఛేంజ్ను సృష్టించడం ఫ్యాక్టరీ యొక్క ప్రధాన విధి. ఎవరైనా ఈ ఫంక్షన్ను కాల్ చేయవచ్చని గమనించండి. కొత్త పెయిర్ ఎక్స్ఛేంజ్ను సృష్టించడానికి మీకు యూనిస్వాప్ నుండి అనుమతి అవసరం లేదు.
require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
(address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
కొత్త ఎక్స్ఛేంజ్ చిరునామా నిర్ణయాత్మకంగా ఉండాలని మేము కోరుకుంటున్నాము, కాబట్టి దీనిని ముందుగానే ఆఫ్చైన్లో లెక్కించవచ్చు (ఇది లేయర్ 2 (l2) లావాదేవీలకు ఉపయోగపడుతుంది). దీన్ని చేయడానికి, మేము వాటిని స్వీకరించిన క్రమంతో సంబంధం లేకుండా టోకెన్ చిరునామాల యొక్క స్థిరమైన క్రమాన్ని కలిగి ఉండాలి, కాబట్టి మేము వాటిని ఇక్కడ క్రమబద్ధీకరిస్తాము.
require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // ఒకే తనిఖీ సరిపోతుంది
చిన్న వాటి కంటే పెద్ద లిక్విడిటీ పూల్స్ మెరుగైనవి, ఎందుకంటే అవి మరింత స్థిరమైన ధరలను కలిగి ఉంటాయి. మేము టోకెన్ల పెయిర్కు ఒకే లిక్విడిటీ పూల్ కంటే ఎక్కువ కలిగి ఉండకూడదనుకుంటున్నాము. ఇప్పటికే ఎక్స్ఛేంజ్ ఉంటే, అదే పెయిర్ కోసం మరొకదాన్ని సృష్టించాల్సిన అవసరం లేదు.
bytes memory bytecode = type(UniswapV2Pair).creationCode;
కొత్త కాంట్రాక్ట్ను సృష్టించడానికి దాన్ని సృష్టించే కోడ్ మాకు అవసరం (కన్స్ట్రక్టర్ ఫంక్షన్ మరియు అసలు కాంట్రాక్ట్ యొక్క EVM బైట్కోడ్ను మెమరీకి వ్రాసే కోడ్ రెండూ). సాధారణంగా Solidity లో మనం కేవలం addr = new <name of contract>(<constructor parameters>) ని ఉపయోగిస్తాము మరియు కంపైలర్ మన కోసం ప్రతిదీ చూసుకుంటుంది, కానీ నిర్ణయాత్మక కాంట్రాక్ట్ చిరునామాను కలిగి ఉండటానికి మనం CREATE2 ఆప్కోడ్ (opens in a new tab) ని ఉపయోగించాలి.
ఈ కోడ్ వ్రాయబడినప్పుడు ఆ ఆప్కోడ్కు Solidity ద్వారా ఇంకా మద్దతు లేదు, కాబట్టి కోడ్ను మాన్యువల్గా పొందడం అవసరం. ఇది ఇకపై సమస్య కాదు, ఎందుకంటే Solidity ఇప్పుడు CREATE2 కి మద్దతు ఇస్తుంది (opens in a new tab).
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
ఆప్కోడ్కు Solidity ద్వారా ఇంకా మద్దతు లేనప్పుడు మనం ఇన్లైన్ అసెంబ్లీ (opens in a new tab) ని ఉపయోగించి దాన్ని కాల్ చేయవచ్చు.
IUniswapV2Pair(pair).initialize(token0, token1);
కొత్త ఎక్స్ఛేంజ్ ఏ రెండు టోకెన్లను మార్పిడి చేస్తుందో చెప్పడానికి initialize ఫంక్షన్ను కాల్ చేయండి.
getPair[token0][token1] = pair;
getPair[token1][token0] = pair; // మ్యాపింగ్ను రివర్స్ దిశలో నింపండి
allPairs.push(pair);
emit PairCreated(token0, token1, pair, allPairs.length);
}
కొత్త పెయిర్ సమాచారాన్ని స్థితి వేరియబుల్స్లో సేవ్ చేయండి మరియు కొత్త పెయిర్ ఎక్స్ఛేంజ్ గురించి ప్రపంచానికి తెలియజేయడానికి ఈవెంట్ను వెలువరించండి.
function setFeeTo(address _feeTo) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeTo = _feeTo;
}
function setFeeToSetter(address _feeToSetter) external {
require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
feeToSetter = _feeToSetter;
}
}
ఈ రెండు ఫంక్షన్లు రుసుము గ్రహీతను (ఎవరైనా ఉంటే) నియంత్రించడానికి మరియు feeSetter ని కొత్త చిరునామాకు మార్చడానికి feeSetter ని అనుమతిస్తాయి.
UniswapV2ERC20.sol
ఈ కాంట్రాక్ట్ (opens in a new tab) ERC-20 ద్రవ్యత టోకెన్ను అమలు చేస్తుంది. ఇది ఓపెన్జెప్పెలిన్ ERC-20 కాంట్రాక్ట్ కి సమానంగా ఉంటుంది, కాబట్టి నేను భిన్నంగా ఉన్న భాగాన్ని, permit కార్యాచరణను మాత్రమే వివరిస్తాను.
ఎథీరియంలో లావాదేవీలకు ఈథర్ (ETH) ఖర్చవుతుంది, ఇది నిజమైన డబ్బుకు సమానం. మీకు ERC-20 టోకెన్లు ఉండి ETH లేకపోతే, మీరు లావాదేవీలను పంపలేరు, కాబట్టి మీరు వాటితో ఏమీ చేయలేరు. ఈ సమస్యను నివారించడానికి ఒక పరిష్కారం మెటా-లావాదేవీలు (opens in a new tab). టోకెన్ల యజమాని ఆఫ్చైన్లో టోకెన్లను ఉపసంహరించుకోవడానికి వేరొకరిని అనుమతించే లావాదేవీపై సంతకం చేస్తారు మరియు ఇంటర్నెట్ను ఉపయోగించి దానిని గ్రహీతకు పంపుతారు. ETH కలిగి ఉన్న గ్రహీత, యజమాని తరపున పర్మిట్ను సమర్పిస్తారు.
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
ఈ హాష్ అనేది లావాదేవీ రకానికి ఐడెంటిఫైయర్ (opens in a new tab). ఈ పారామితులతో మేము ఇక్కడ మద్దతు ఇచ్చే ఏకైక రకం Permit.
mapping(address => uint) public nonces;
గ్రహీత డిజిటల్ సంతకాన్ని నకిలీ చేయడం సాధ్యం కాదు. అయితే, అదే లావాదేవీని రెండుసార్లు పంపడం చాలా సులభం (ఇది ఒక రకమైన రీప్లే దాడి (opens in a new tab)). దీనిని నివారించడానికి, మేము నాన్స్ (opens in a new tab) ని ఉపయోగిస్తాము. కొత్త Permit యొక్క నాన్స్ చివరిగా ఉపయోగించిన దానికంటే ఒకటి ఎక్కువ కాకపోతే, అది చెల్లదని మేము భావిస్తాము.
constructor() public {
uint chainId;
assembly {
chainId := chainid
}
చైన్ ఐడెంటిఫైయర్ను (opens in a new tab) తిరిగి పొందడానికి ఇది కోడ్. ఇది Yul (opens in a new tab) అనే EVM అసెంబ్లీ మాండలికాన్ని ఉపయోగిస్తుంది. Yul యొక్క ప్రస్తుత వెర్షన్లో మీరు chainid కాకుండా chainid() ని ఉపయోగించాలని గమనించండి.
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
keccak256(bytes(name)),
keccak256(bytes('1')),
chainId,
address(this)
)
);
}
EIP-712 కోసం డొమైన్ సెపరేటర్ను (opens in a new tab) లెక్కించండి.
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
ఇది అనుమతులను అమలు చేసే ఫంక్షన్. ఇది సంబంధిత ఫీల్డ్లను మరియు సంతకం (opens in a new tab) కోసం మూడు స్కేలార్ విలువలను (v, r, మరియు s) పారామితులుగా స్వీకరిస్తుంది.
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
గడువు ముగిసిన తర్వాత లావాదేవీలను అంగీకరించవద్దు.
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
abi.encodePacked(...) అనేది మేము పొందాలని ఆశించే సందేశం. నాన్స్ ఏమిటో మాకు తెలుసు, కాబట్టి దానిని పారామితిగా పొందాల్సిన అవసరం మాకు లేదు.
ఎథీరియం సంతకం అల్గారిథమ్ సంతకం చేయడానికి 256 బిట్లను పొందుతుందని ఆశిస్తుంది, కాబట్టి మేము keccak256 హాష్ ఫంక్షన్ను ఉపయోగిస్తాము.
address recoveredAddress = ecrecover(digest, v, r, s);
డైజెస్ట్ మరియు సంతకం నుండి మనం ecrecover (opens in a new tab) ని ఉపయోగించి సంతకం చేసిన చిరునామాను పొందవచ్చు.
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
అంతా సవ్యంగా ఉంటే, దీనిని ERC-20 ఆమోదించు (opens in a new tab) గా పరిగణించండి.
పెరిఫెరీ కాంట్రాక్ట్లు
పెరిఫెరీ కాంట్రాక్ట్లు యూనిస్వాప్ కోసం API (అప్లికేషన్ ప్రోగ్రామ్ ఇంటర్ఫేస్). ఇవి ఇతర కాంట్రాక్ట్లు లేదా వికేంద్రీకృత అప్లికేషన్ల (dapps) నుండి బాహ్య కాల్ల కోసం అందుబాటులో ఉంటాయి. మీరు నేరుగా కోర్ కాంట్రాక్ట్లను కాల్ చేయవచ్చు, కానీ అది మరింత సంక్లిష్టమైనది మరియు మీరు పొరపాటు చేస్తే విలువను కోల్పోవచ్చు. కోర్ కాంట్రాక్ట్లు మోసపోకుండా చూసుకోవడానికి మాత్రమే పరీక్షలను కలిగి ఉంటాయి, ఇతరుల కోసం ఎలాంటి శానిటీ చెక్లను కలిగి ఉండవు. అవి పెరిఫెరీలో ఉంటాయి కాబట్టి అవసరమైనప్పుడు వాటిని అప్డేట్ చేయవచ్చు.
UniswapV2Router01.sol
ఈ కాంట్రాక్ట్ (opens in a new tab)లో సమస్యలు ఉన్నాయి, మరియు దీనిని ఇకపై ఉపయోగించకూడదు (opens in a new tab). అదృష్టవశాత్తూ, పెరిఫెరీ కాంట్రాక్ట్లు స్థితిరహితమైనవి (stateless) మరియు ఎలాంటి ఆస్తులను కలిగి ఉండవు, కాబట్టి దీనిని నిలిపివేయడం మరియు దానికి బదులుగా UniswapV2Router02ని ఉపయోగించమని ప్రజలకు సూచించడం సులభం.
UniswapV2Router02.sol
చాలా సందర్భాలలో మీరు ఈ కాంట్రాక్ట్ (opens in a new tab) ద్వారా యూనిస్వాప్ని ఉపయోగిస్తారు. దీనిని ఎలా ఉపయోగించాలో మీరు ఇక్కడ (opens in a new tab) చూడవచ్చు.
pragma solidity =0.6.6;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol';
import '@uniswap/lib/contracts/libraries/TransferHelper.sol';
import './interfaces/IUniswapV2Router02.sol';
import './libraries/UniswapV2Library.sol';
import './libraries/SafeMath.sol';
import './interfaces/IERC20.sol';
import './interfaces/IWETH.sol';
వీటిలో చాలా వాటిని మనం ఇంతకు ముందే చూశాము లేదా అవి చాలా స్పష్టంగా ఉన్నాయి. దీనికి ఒక మినహాయింపు IWETH.sol. యూనిస్వాప్ v2 ఏదైనా ERC-20 టోకెన్ల జతకు మార్పిడిని అనుమతిస్తుంది, కానీ ఈథర్ (ETH) స్వయంగా ERC-20 టోకెన్ కాదు. ఇది ఆ ప్రమాణానికి ముందే వచ్చింది మరియు ప్రత్యేకమైన యంత్రాంగాల ద్వారా బదిలీ చేయబడుతుంది. ERC-20 టోకెన్లకు వర్తించే కాంట్రాక్ట్లలో ETH వినియోగాన్ని ప్రారంభించడానికి ప్రజలు ర్యాప్డ్ ఈథర్ (weth) (opens in a new tab) కాంట్రాక్ట్ను తీసుకువచ్చారు. మీరు ఈ కాంట్రాక్ట్కు ETH పంపుతారు, మరియు ఇది మీకు సమానమైన WETHని ముద్రిస్తుంది. లేదా మీరు WETHని దహనం చేసి, తిరిగి ETHని పొందవచ్చు.
contract UniswapV2Router02 is IUniswapV2Router02 {
using SafeMath for uint;
address public immutable override factory;
address public immutable override WETH;
రూటర్ ఏ ఫ్యాక్టరీని ఉపయోగించాలో తెలుసుకోవాలి, మరియు WETH అవసరమయ్యే లావాదేవీల కోసం ఏ WETH కాంట్రాక్ట్ను ఉపయోగించాలో తెలుసుకోవాలి. ఈ విలువలు మార్చలేనివి (opens in a new tab), అంటే వాటిని కన్స్ట్రక్టర్లో మాత్రమే సెట్ చేయవచ్చు. ఇది తక్కువ నిజాయితీ గల కాంట్రాక్ట్లను సూచించేలా ఎవరూ వాటిని మార్చలేరనే నమ్మకాన్ని వినియోగదారులకు ఇస్తుంది.
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
సమయ పరిమితి ఉన్న లావాదేవీలు ("వీలైతే Y సమయానికి ముందు X చేయండి") వాటి సమయ పరిమితి తర్వాత జరగకుండా ఈ మాడిఫైయర్ నిర్ధారిస్తుంది.
constructor(address _factory, address _WETH) public {
factory = _factory;
WETH = _WETH;
}
కన్స్ట్రక్టర్ కేవలం మార్చలేని స్థితి వేరియబుల్స్ను సెట్ చేస్తుంది.
receive() external payable {
assert(msg.sender == WETH); // WETH కాంట్రాక్ట్ నుండి ఫాల్బ్యాక్ ద్వారా మాత్రమే ETH ని అంగీకరించండి
}
మనం WETH కాంట్రాక్ట్ నుండి టోకెన్లను తిరిగి ETHలోకి రీడీమ్ చేసినప్పుడు ఈ ఫంక్షన్ కాల్ చేయబడుతుంది. మనం ఉపయోగించే WETH కాంట్రాక్ట్కు మాత్రమే అలా చేయడానికి అధికారం ఉంటుంది.
ద్రవ్యతను జోడించడం
ఈ ఫంక్షన్లు పెయిర్ ఎక్స్ఛేంజ్కు టోకెన్లను జోడిస్తాయి, ఇది లిక్విడిటీ పూల్ను పెంచుతుంది.
// **** ద్రవ్యతను జోడించండి ****
function _addLiquidity(
పెయిర్ ఎక్స్ఛేంజ్లో డిపాజిట్ చేయాల్సిన A మరియు B టోకెన్ల మొత్తాన్ని లెక్కించడానికి ఈ ఫంక్షన్ ఉపయోగించబడుతుంది.
address tokenA,
address tokenB,
ఇవి ERC-20 టోకెన్ కాంట్రాక్ట్ల చిరునామాలు.
uint amountADesired,
uint amountBDesired,
ఇవి ద్రవ్యత సమకూర్చేవారు డిపాజిట్ చేయాలనుకుంటున్న మొత్తాలు. అవి డిపాజిట్ చేయాల్సిన A మరియు B గరిష్ట మొత్తాలు కూడా.
uint amountAMin,
uint amountBMin
ఇవి డిపాజిట్ చేయడానికి ఆమోదయోగ్యమైన కనీస మొత్తాలు. ఈ మొత్తాలు లేదా అంతకంటే ఎక్కువ మొత్తాలతో లావాదేవీ జరగకపోతే, దాని నుండి రివర్ట్ చేయండి. మీకు ఈ ఫీచర్ వద్దు అనుకుంటే, సున్నా అని పేర్కొనండి.
ద్రవ్యత సమకూర్చేవారు సాధారణంగా కనీస మొత్తాన్ని పేర్కొంటారు, ఎందుకంటే వారు లావాదేవీని ప్రస్తుత మారకపు రేటుకు దగ్గరగా ఉండేలా పరిమితం చేయాలనుకుంటారు. మారకపు రేటు చాలా ఎక్కువగా హెచ్చుతగ్గులకు గురైతే, అది అంతర్లీన విలువలను మార్చే వార్తలను సూచించవచ్చు, మరియు వారు ఏమి చేయాలో మాన్యువల్గా నిర్ణయించుకోవాలనుకుంటారు.
ఉదాహరణకు, మారకపు రేటు ఒకటికి ఒకటిగా ఉన్నప్పుడు మరియు ద్రవ్యత సమకూర్చేవారు ఈ విలువలను పేర్కొన్న సందర్భాన్ని ఊహించండి:
| పరామితి (Parameter) | విలువ |
|---|---|
| amountADesired | 1000 |
| amountBDesired | 1000 |
| amountAMin | 900 |
| amountBMin | 800 |
మారకపు రేటు 0.9 మరియు 1.25 మధ్య ఉన్నంత వరకు, లావాదేవీ జరుగుతుంది. మారకపు రేటు ఆ పరిధి దాటితే, లావాదేవీ రద్దు చేయబడుతుంది.
ఈ ముందుజాగ్రత్తకు కారణం ఏమిటంటే లావాదేవీలు తక్షణమే జరగవు, మీరు వాటిని సమర్పిస్తారు మరియు చివరికి ఒక ధృవీకర్త వాటిని బ్లాక్లో చేరుస్తారు (మీ గ్యాస్ ధర చాలా తక్కువగా ఉంటే తప్ప, ఆ సందర్భంలో మీరు దానిని ఓవర్రైట్ చేయడానికి అదే నాన్స్తో మరియు అధిక గ్యాస్ ధరతో మరొక లావాదేవీని సమర్పించాల్సి ఉంటుంది). సమర్పణ మరియు చేరిక మధ్య వ్యవధిలో ఏమి జరుగుతుందో మీరు నియంత్రించలేరు.
) internal virtual returns (uint amountA, uint amountB) {
రిజర్వ్ల మధ్య ప్రస్తుత నిష్పత్తికి సమానమైన నిష్పత్తిని కలిగి ఉండటానికి ద్రవ్యత సమకూర్చేవారు డిపాజిట్ చేయాల్సిన మొత్తాలను ఈ ఫంక్షన్ అందిస్తుంది.
// పెయిర్ ఇంకా ఉనికిలో లేకపోతే దాన్ని సృష్టించండి
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
ఈ టోకెన్ జతకు ఇంకా ఎక్స్ఛేంజ్ లేకపోతే, దానిని సృష్టించండి.
(uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
జతలోని ప్రస్తుత రిజర్వ్లను పొందండి.
if (reserveA == 0 && reserveB == 0) {
(amountA, amountB) = (amountADesired, amountBDesired);
ప్రస్తుత రిజర్వ్లు ఖాళీగా ఉంటే, ఇది కొత్త పెయిర్ ఎక్స్ఛేంజ్. డిపాజిట్ చేయాల్సిన మొత్తాలు ద్రవ్యత సమకూర్చేవారు అందించాలనుకుంటున్న మొత్తాలకు సరిగ్గా సమానంగా ఉండాలి.
} else {
uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
మొత్తాలు ఎలా ఉంటాయో మనం చూడవలసి వస్తే, ఈ ఫంక్షన్ (opens in a new tab)ని ఉపయోగించి మనం సరైన మొత్తాన్ని పొందుతాము. ప్రస్తుత రిజర్వ్ల మాదిరిగానే అదే నిష్పత్తి మాకు కావాలి.
if (amountBOptimal <= amountBDesired) {
require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
(amountA, amountB) = (amountADesired, amountBOptimal);
amountBOptimal అనేది ద్రవ్యత సమకూర్చేవారు డిపాజిట్ చేయాలనుకుంటున్న మొత్తం కంటే తక్కువగా ఉంటే, ద్రవ్యత డిపాజిటర్ అనుకున్నదానికంటే టోకెన్ B ప్రస్తుతం ఎక్కువ విలువైనదని అర్థం, కాబట్టి తక్కువ మొత్తం అవసరం.
} else {
uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
assert(amountAOptimal <= amountADesired);
require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
(amountA, amountB) = (amountAOptimal, amountBDesired);
సరైన B మొత్తం ఆశించిన B మొత్తం కంటే ఎక్కువగా ఉంటే, ద్రవ్యత డిపాజిటర్ అనుకున్నదానికంటే B టోకెన్లు ప్రస్తుతం తక్కువ విలువైనవని అర్థం, కాబట్టి ఎక్కువ మొత్తం అవసరం. అయినప్పటికీ, ఆశించిన మొత్తం గరిష్టమైనది, కాబట్టి మనం అలా చేయలేము. బదులుగా మనం ఆశించిన B టోకెన్ల మొత్తం కోసం సరైన A టోకెన్ల సంఖ్యను లెక్కిస్తాము.
వీటన్నింటినీ కలిపితే మనకు ఈ గ్రాఫ్ వస్తుంది. మీరు వెయ్యి A టోకెన్లను (నీలి రంగు గీత) మరియు వెయ్యి B టోకెన్లను (ఎరుపు రంగు గీత) డిపాజిట్ చేయడానికి ప్రయత్నిస్తున్నారని అనుకుందాం. x అక్షం అనేది మారకపు రేటు, A/B. x=1 అయితే, అవి సమాన విలువను కలిగి ఉంటాయి మరియు మీరు ఒక్కొక్కటి వెయ్యి డిపాజిట్ చేస్తారు. x=2 అయితే, A అనేది B కంటే రెట్టింపు విలువను కలిగి ఉంటుంది (ప్రతి A టోకెన్కు మీకు రెండు B టోకెన్లు లభిస్తాయి) కాబట్టి మీరు వెయ్యి B టోకెన్లను డిపాజిట్ చేస్తారు, కానీ 500 A టోకెన్లను మాత్రమే డిపాజిట్ చేస్తారు. x=0.5 అయితే, పరిస్థితి తారుమారు అవుతుంది, వెయ్యి A టోకెన్లు మరియు ఐదు వందల B టోకెన్లు.
మీరు ద్రవ్యతను నేరుగా కోర్ కాంట్రాక్ట్లోకి డిపాజిట్ చేయవచ్చు (UniswapV2Pair::mint (opens in a new tab)ని ఉపయోగించి), కానీ కోర్ కాంట్రాక్ట్ తాను మోసపోవడం లేదని మాత్రమే తనిఖీ చేస్తుంది, కాబట్టి మీరు మీ లావాదేవీని సమర్పించిన సమయానికి మరియు అది అమలు చేయబడే సమయానికి మధ్య మారకపు రేటు మారితే విలువను కోల్పోయే ప్రమాదం ఉంది. మీరు పెరిఫెరీ కాంట్రాక్ట్ను ఉపయోగిస్తే, అది మీరు డిపాజిట్ చేయాల్సిన మొత్తాన్ని లెక్కిస్తుంది మరియు వెంటనే దానిని డిపాజిట్ చేస్తుంది, కాబట్టి మారకపు రేటు మారదు మరియు మీరు దేనినీ కోల్పోరు.
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
ద్రవ్యతను డిపాజిట్ చేయడానికి లావాదేవీ ద్వారా ఈ ఫంక్షన్ను కాల్ చేయవచ్చు. రెండు మినహాయింపులతో, చాలా పారామితులు పైన ఉన్న _addLiquidityలో ఉన్నట్లే ఉంటాయి:
. to అనేది పూల్లో ద్రవ్యత సమకూర్చేవారి వాటాను చూపించడానికి ముద్రించబడిన కొత్త ద్రవ్యత టోకెన్లను పొందే చిరునామా
. deadline అనేది లావాదేవీపై సమయ పరిమితి
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
మనం వాస్తవానికి డిపాజిట్ చేయాల్సిన మొత్తాలను లెక్కిస్తాము మరియు ఆపై లిక్విడిటీ పూల్ చిరునామాను కనుగొంటాము. గ్యాస్ను ఆదా చేయడానికి మనం ఫ్యాక్టరీని అడగడం ద్వారా కాకుండా, లైబ్రరీ ఫంక్షన్ pairForని ఉపయోగించి దీన్ని చేస్తాము (లైబ్రరీలలో క్రింద చూడండి)
TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
వినియోగదారు నుండి సరైన మొత్తంలో టోకెన్లను పెయిర్ ఎక్స్ఛేంజ్లోకి బదిలీ చేయండి.
liquidity = IUniswapV2Pair(pair).mint(to);
}
దీనికి ప్రతిఫలంగా పూల్ యొక్క పాక్షిక యాజమాన్యం కోసం to చిరునామాకు ద్రవ్యత టోకెన్లను ఇవ్వండి. కోర్ కాంట్రాక్ట్ యొక్క mint ఫంక్షన్ దాని వద్ద ఎన్ని అదనపు టోకెన్లు ఉన్నాయో చూస్తుంది (గతంలో ద్రవ్యత మారినప్పుడు ఉన్నదానితో పోలిస్తే) మరియు దానికి అనుగుణంగా ద్రవ్యతను ముద్రిస్తుంది.
function addLiquidityETH(
address token,
uint amountTokenDesired,
ద్రవ్యత సమకూర్చేవారు టోకెన్/ETH పెయిర్ ఎక్స్ఛేంజ్కు ద్రవ్యతను అందించాలనుకున్నప్పుడు, కొన్ని తేడాలు ఉంటాయి. ద్రవ్యత సమకూర్చేవారి కోసం ETHని ర్యాప్ చేయడాన్ని కాంట్రాక్ట్ నిర్వహిస్తుంది. వినియోగదారు ఎన్ని ETH డిపాజిట్ చేయాలనుకుంటున్నారో పేర్కొనవలసిన అవసరం లేదు, ఎందుకంటే వినియోగదారు వాటిని లావాదేవీతో పాటు పంపుతారు (మొత్తం msg.valueలో అందుబాటులో ఉంటుంది).
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
(amountToken, amountETH) = _addLiquidity(
token,
WETH,
amountTokenDesired,
msg.value,
amountTokenMin,
amountETHMin
);
address pair = UniswapV2Library.pairFor(factory, token, WETH);
TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
IWETH(WETH).deposit{value: amountETH}();
assert(IWETH(WETH).transfer(pair, amountETH));
ETHని డిపాజిట్ చేయడానికి కాంట్రాక్ట్ ముందుగా దానిని WETHగా ర్యాప్ చేస్తుంది మరియు ఆపై WETHని జతలోకి బదిలీ చేస్తుంది. బదిలీ assertలో ర్యాప్ చేయబడిందని గమనించండి. దీని అర్థం బదిలీ విఫలమైతే ఈ కాంట్రాక్ట్ కాల్ కూడా విఫలమవుతుంది, కాబట్టి ర్యాపింగ్ నిజంగా జరగదు.
liquidity = IUniswapV2Pair(pair).mint(to);
// డస్ట్ eth ఏమైనా ఉంటే, వాపసు చేయండి
if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
}
వినియోగదారు ఇప్పటికే మాకు ETH పంపారు, కాబట్టి ఏదైనా అదనంగా మిగిలి ఉంటే (ఎందుకంటే ఇతర టోకెన్ వినియోగదారు అనుకున్నదానికంటే తక్కువ విలువైనది), మనం వాపసు జారీ చేయాలి.
ద్రవ్యతను తీసివేయడం
ఈ ఫంక్షన్లు ద్రవ్యతను తీసివేస్తాయి మరియు ద్రవ్యత సమకూర్చేవారికి తిరిగి చెల్లిస్తాయి.
// **** ద్రవ్యతను తీసివేయండి ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
ద్రవ్యతను తీసివేయడానికి అత్యంత సులభమైన సందర్భం. ద్రవ్యత సమకూర్చేవారు అంగీకరించే ప్రతి టోకెన్ యొక్క కనీస మొత్తం ఉంటుంది, మరియు అది గడువుకు ముందే జరగాలి.
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // పెయిర్కు ద్రవ్యతను పంపండి
(uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
కోర్ కాంట్రాక్ట్ యొక్క burn ఫంక్షన్ వినియోగదారుకు టోకెన్లను తిరిగి చెల్లించడాన్ని నిర్వహిస్తుంది.
(address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
ఒక ఫంక్షన్ బహుళ విలువలను అందించినప్పుడు, కానీ మనకు వాటిలో కొన్ని మాత్రమే కావాలనుకున్నప్పుడు, మనం ఆ విలువలను మాత్రమే ఈ విధంగా పొందుతాము. విలువను చదవడం మరియు దానిని ఎప్పుడూ ఉపయోగించకపోవడం కంటే గ్యాస్ పరంగా ఇది కొంత చౌకైనది.
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
కోర్ కాంట్రాక్ట్ వాటిని అందించే విధానం (ముందుగా తక్కువ చిరునామా టోకెన్) నుండి వినియోగదారు ఆశించే విధానానికి (tokenA మరియు tokenBకి అనుగుణంగా) మొత్తాలను అనువదించండి.
require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
}
ముందుగా బదిలీ చేయడం మరియు ఆపై అది చట్టబద్ధమైనదో కాదో ధృవీకరించడం సబబే, ఎందుకంటే అది కాకపోతే మనం అన్ని స్థితి మార్పుల నుండి రివర్ట్ చేస్తాము.
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
(amountToken, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, amountToken);
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
ETH కోసం ద్రవ్యతను తీసివేయడం దాదాపు ఒకే విధంగా ఉంటుంది, కాకపోతే మనం WETH టోకెన్లను స్వీకరిస్తాము మరియు ద్రవ్యత సమకూర్చేవారికి తిరిగి ఇవ్వడానికి వాటిని ETH కోసం రీడీమ్ చేస్తాము.
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountA, uint amountB) {
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
}
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountToken, uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
(amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
}
ఈథర్ లేని వినియోగదారులు పర్మిట్ మెకానిజంని ఉపయోగించి పూల్ నుండి ఉపసంహరించుకోవడానికి ఈ ఫంక్షన్లు మెటా-లావాదేవీలను ప్రసారం చేస్తాయి.
// **** ద్రవ్యతను తీసివేయండి (బదిలీపై-రుసుము టోకెన్లకు మద్దతు ఇస్తుంది) ****
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountETH) {
(, amountETH) = removeLiquidity(
token,
WETH,
liquidity,
amountTokenMin,
amountETHMin,
address(this),
deadline
);
TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
IWETH(WETH).withdraw(amountETH);
TransferHelper.safeTransferETH(to, amountETH);
}
బదిలీ లేదా నిల్వ రుసుములను కలిగి ఉన్న టోకెన్ల కోసం ఈ ఫంక్షన్ను ఉపయోగించవచ్చు. టోకెన్కు అటువంటి రుసుములు ఉన్నప్పుడు, మనం ఎంత టోకెన్ను తిరిగి పొందుతామో చెప్పడానికి మనం removeLiquidity ఫంక్షన్పై ఆధారపడలేము, కాబట్టి మనం ముందుగా ఉపసంహరించుకోవాలి మరియు ఆపై బ్యాలెన్స్ను పొందాలి.
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax, uint8 v, bytes32 r, bytes32 s
) external virtual override returns (uint amountETH) {
address pair = UniswapV2Library.pairFor(factory, token, WETH);
uint value = approveMax ? uint(-1) : liquidity;
IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
token, liquidity, amountTokenMin, amountETHMin, to, deadline
);
}
చివరి ఫంక్షన్ నిల్వ రుసుములను మెటా-లావాదేవీలతో కలుపుతుంది.
ట్రేడ్
// **** మార్పిడి ****
// ప్రారంభ మొత్తం ఇప్పటికే మొదటి పెయిర్కు పంపబడి ఉండాలి
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
ట్రేడర్లకు బహిర్గతమయ్యే ఫంక్షన్లకు అవసరమైన అంతర్గత ప్రాసెసింగ్ను ఈ ఫంక్షన్ చేస్తుంది.
for (uint i; i < path.length - 1; i++) {
నేను దీన్ని రాస్తున్న సమయానికి 388,160 ERC-20 టోకెన్లు (opens in a new tab) ఉన్నాయి. ప్రతి టోకెన్ జతకు ఒక పెయిర్ ఎక్స్ఛేంజ్ ఉంటే, అది 150 బిలియన్లకు పైగా పెయిర్ ఎక్స్ఛేంజ్లు అవుతుంది. ప్రస్తుతం మొత్తం చైన్లో, ఆ ఖాతాల సంఖ్యలో 0.1% మాత్రమే ఉన్నాయి (opens in a new tab). దీనికి బదులుగా, మార్పిడి ఫంక్షన్లు పాత్ (మార్గం) అనే భావనకు మద్దతు ఇస్తాయి. ఒక ట్రేడర్ Aని Bకి, Bని Cకి, మరియు Cని Dకి మార్పిడి చేయవచ్చు, కాబట్టి నేరుగా A-D పెయిర్ ఎక్స్ఛేంజ్ అవసరం లేదు.
ఈ మార్కెట్లలో ధరలు సమకాలీకరించబడే అవకాశం ఉంది, ఎందుకంటే అవి సమకాలీకరణలో లేనప్పుడు అది ఆర్బిట్రేజ్కు అవకాశాన్ని సృష్టిస్తుంది. ఉదాహరణకు, A, B, మరియు C అనే మూడు టోకెన్లను ఊహించండి. ప్రతి జతకు ఒకటి చొప్పున మూడు పెయిర్ ఎక్స్ఛేంజ్లు ఉన్నాయి.
- ప్రారంభ పరిస్థితి
- ఒక ట్రేడర్ 24.695 A టోకెన్లను విక్రయించి 25.305 B టోకెన్లను పొందుతాడు.
- ట్రేడర్ 24.695 B టోకెన్లను 25.305 C టోకెన్లకు విక్రయిస్తాడు, సుమారు 0.61 B టోకెన్లను లాభంగా ఉంచుకుంటాడు.
- ఆ తర్వాత ట్రేడర్ 24.695 C టోకెన్లను 25.305 A టోకెన్లకు విక్రయిస్తాడు, సుమారు 0.61 C టోకెన్లను లాభంగా ఉంచుకుంటాడు. ట్రేడర్ వద్ద 0.61 అదనపు A టోకెన్లు కూడా ఉంటాయి (ట్రేడర్ చివరకు పొందే 25.305, మైనస్ అసలు పెట్టుబడి 24.695).
| దశ (Step) | A-B ఎక్స్ఛేంజ్ | B-C ఎక్స్ఛేంజ్ | A-C ఎక్స్ఛేంజ్ |
|---|---|---|---|
| 1 | A:1000 B:1050 A/B=1.05 | B:1000 C:1050 B/C=1.05 | A:1050 C:1000 C/A=1.05 |
| 2 | A:1024.695 B:1024.695 A/B=1 | B:1000 C:1050 B/C=1.05 | A:1050 C:1000 C/A=1.05 |
| 3 | A:1024.695 B:1024.695 A/B=1 | B:1024.695 C:1024.695 B/C=1 | A:1050 C:1000 C/A=1.05 |
| 4 | A:1024.695 B:1024.695 A/B=1 | B:1024.695 C:1024.695 B/C=1 | A:1024.695 C:1024.695 C/A=1 |
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
మనం ప్రస్తుతం నిర్వహిస్తున్న జతను పొందండి, దానిని క్రమబద్ధీకరించండి (జతతో ఉపయోగించడానికి) మరియు ఆశించిన అవుట్పుట్ మొత్తాన్ని పొందండి.
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
పెయిర్ ఎక్స్ఛేంజ్ ఆశించే విధంగా క్రమబద్ధీకరించబడిన ఆశించిన అవుట్ మొత్తాలను పొందండి.
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
ఇది చివరి ఎక్స్ఛేంజ్ ఆ? అలా అయితే, ట్రేడ్ కోసం స్వీకరించిన టోకెన్లను గమ్యస్థానానికి పంపండి. లేకపోతే, దానిని తదుపరి పెయిర్ ఎక్స్ఛేంజ్కు పంపండి.
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
టోకెన్లను మార్పిడి చేయడానికి వాస్తవానికి పెయిర్ ఎక్స్ఛేంజ్ను కాల్ చేయండి. ఎక్స్ఛేంజ్ గురించి మాకు చెప్పడానికి కాల్బ్యాక్ అవసరం లేదు, కాబట్టి మేము ఆ ఫీల్డ్లో ఎలాంటి బైట్లను పంపము.
function swapExactTokensForTokens(
ఒక టోకెన్ను మరొకదానికి మార్పిడి చేయడానికి ట్రేడర్లు నేరుగా ఈ ఫంక్షన్ను ఉపయోగిస్తారు.
uint amountIn,
uint amountOutMin,
address[] calldata path,
ఈ పరామితి ERC-20 కాంట్రాక్ట్ల చిరునామాలను కలిగి ఉంటుంది. పైన వివరించినట్లుగా, ఇది ఒక శ్రేణి (array) ఎందుకంటే మీ వద్ద ఉన్న ఆస్తి నుండి మీకు కావలసిన ఆస్తికి వెళ్లడానికి మీరు అనేక పెయిర్ ఎక్స్ఛేంజ్ల గుండా వెళ్లవలసి ఉంటుంది.
Solidityలో ఫంక్షన్ పరామితిని memory లేదా calldataలో నిల్వ చేయవచ్చు. ఫంక్షన్ అనేది కాంట్రాక్ట్కు ఎంట్రీ పాయింట్ అయితే, వినియోగదారు నుండి (లావాదేవీని ఉపయోగించి) లేదా వేరే కాంట్రాక్ట్ నుండి నేరుగా కాల్ చేయబడితే, పరామితి విలువను నేరుగా కాల్ డేటా నుండి తీసుకోవచ్చు. పైన ఉన్న _swap వలె ఫంక్షన్ అంతర్గతంగా కాల్ చేయబడితే, పారామితులను memoryలో నిల్వ చేయాలి. కాల్ చేయబడిన కాంట్రాక్ట్ కోణం నుండి calldata అనేది చదవడానికి మాత్రమే (read only).
uint లేదా address వంటి స్కేలార్ రకాలతో కంపైలర్ మన కోసం నిల్వ ఎంపికను నిర్వహిస్తుంది, కానీ పొడవైన మరియు ఖరీదైన శ్రేణులతో, మనం ఉపయోగించాల్సిన నిల్వ రకాన్ని పేర్కొంటాము.
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
రిటర్న్ విలువలు ఎల్లప్పుడూ మెమరీలో అందించబడతాయి.
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
ప్రతి మార్పిడిలో కొనుగోలు చేయాల్సిన మొత్తాన్ని లెక్కించండి. ఫలితం ట్రేడర్ అంగీకరించడానికి సిద్ధంగా ఉన్న కనీస మొత్తం కంటే తక్కువగా ఉంటే, లావాదేవీ నుండి రివర్ట్ చేయండి.
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
చివరగా, మొదటి పెయిర్ ఎక్స్ఛేంజ్ కోసం ప్రారంభ ERC-20 టోకెన్ను ఖాతాకు బదిలీ చేయండి మరియు _swapని కాల్ చేయండి. ఇదంతా ఒకే లావాదేవీలో జరుగుతోంది, కాబట్టి ఊహించని టోకెన్లు ఏవైనా ఈ బదిలీలో భాగమేనని పెయిర్ ఎక్స్ఛేంజ్కు తెలుసు.
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
మునుపటి ఫంక్షన్, swapTokensForTokens, ట్రేడర్ తాను ఇవ్వడానికి సిద్ధంగా ఉన్న ఇన్పుట్ టోకెన్ల ఖచ్చితమైన సంఖ్యను మరియు దానికి ప్రతిఫలంగా తాను స్వీకరించడానికి సిద్ధంగా ఉన్న అవుట్పుట్ టోకెన్ల కనీస సంఖ్యను పేర్కొనడానికి అనుమతిస్తుంది. ఈ ఫంక్షన్ రివర్స్ మార్పిడిని చేస్తుంది, ఇది ట్రేడర్ తనకు కావలసిన అవుట్పుట్ టోకెన్ల సంఖ్యను మరియు వాటి కోసం తాను చెల్లించడానికి సిద్ధంగా ఉన్న ఇన్పుట్ టోకెన్ల గరిష్ట సంఖ్యను పేర్కొనడానికి అనుమతిస్తుంది.
రెండు సందర్భాల్లోనూ, ట్రేడర్ ముందుగా ఈ పెరిఫెరీ కాంట్రాక్ట్కు వాటిని బదిలీ చేయడానికి అనుమతి మొత్తాన్ని ఇవ్వాలి.
function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
}
function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= amountInMax, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
external
virtual
override
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, address(this));
IWETH(WETH).withdraw(amounts[amounts.length - 1]);
TransferHelper.safeTransferETH(to, amounts[amounts.length - 1]);
}
function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
external
virtual
override
payable
ensure(deadline)
returns (uint[] memory amounts)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
amounts = UniswapV2Library.getAmountsIn(factory, amountOut, path);
require(amounts[0] <= msg.value, 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT');
IWETH(WETH).deposit{value: amounts[0]}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
_swap(amounts, path, to);
// డస్ట్ eth ఏమైనా ఉంటే, వాపసు చేయండి
if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
}
ఈ నాలుగు వేరియంట్లు అన్నీ ETH మరియు టోకెన్ల మధ్య ట్రేడింగ్ను కలిగి ఉంటాయి. ఒకే తేడా ఏమిటంటే, మనం ట్రేడర్ నుండి ETHని స్వీకరించి దానిని WETHని ముద్రించడానికి ఉపయోగిస్తాము, లేదా మార్గంలోని చివరి ఎక్స్ఛేంజ్ నుండి WETHని స్వీకరించి దానిని దహనం చేసి, ఫలితంగా వచ్చిన ETHని ట్రేడర్కు తిరిగి పంపుతాము.
// **** మార్పిడి (బదిలీపై-రుసుము టోకెన్లకు మద్దతు ఇస్తుంది) ****
// ప్రారంభ మొత్తం ఇప్పటికే మొదటి పెయిర్కు పంపబడి ఉండాలి
function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {
(ఈ సమస్య (opens in a new tab))ను పరిష్కరించడానికి బదిలీ లేదా నిల్వ రుసుములను కలిగి ఉన్న టోకెన్లను మార్పిడి చేయడానికి ఇది అంతర్గత ఫంక్షన్.
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
IUniswapV2Pair pair = IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output));
uint amountInput;
uint amountOutput;
{ // స్టాక్ టూ డీప్ ఎర్రర్లను నివారించడానికి స్కోప్
(uint reserve0, uint reserve1,) = pair.getReserves();
(uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
amountInput = IERC20(input).balanceOf(address(pair)).sub(reserveInput);
amountOutput = UniswapV2Library.getAmountOut(amountInput, reserveInput, reserveOutput);
బదిలీ రుసుముల కారణంగా ప్రతి బదిలీ నుండి మనం ఎంత పొందుతామో చెప్పడానికి మనం getAmountsOut ఫంక్షన్పై ఆధారపడలేము (అసలు _swapని కాల్ చేయడానికి ముందు మనం చేసే విధంగా). బదులుగా మనం ముందుగా బదిలీ చేయాలి మరియు ఆపై మనకు ఎన్ని టోకెన్లు తిరిగి వచ్చాయో చూడాలి.
గమనిక: సిద్ధాంతపరంగా మనం _swapకి బదులుగా ఈ ఫంక్షన్ను ఉపయోగించవచ్చు, కానీ కొన్ని సందర్భాల్లో (ఉదాహరణకు, అవసరమైన కనీస మొత్తాన్ని చేరుకోవడానికి చివర్లో తగినంత లేనందున బదిలీ రివర్ట్ చేయబడితే) అది ఎక్కువ గ్యాస్ ఖర్చుకు దారి తీస్తుంది. బదిలీ రుసుము టోకెన్లు చాలా అరుదు, కాబట్టి మనం వాటికి వసతి కల్పించాల్సిన అవసరం ఉన్నప్పటికీ, అన్ని మార్పిడిలు కనీసం వాటిలో ఒకదాని గుండా వెళతాయని భావించాల్సిన అవసరం లేదు.
}
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOutput) : (amountOutput, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
pair.swap(amount0Out, amount1Out, to, new bytes(0));
}
}
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) {
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
payable
ensure(deadline)
{
require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
uint amountIn = msg.value;
IWETH(WETH).deposit{value: amountIn}();
assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn));
uint balanceBefore = IERC20(path[path.length - 1]).balanceOf(to);
_swapSupportingFeeOnTransferTokens(path, to);
require(
IERC20(path[path.length - 1]).balanceOf(to).sub(balanceBefore) >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
}
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
)
external
virtual
override
ensure(deadline)
{
require(path[path.length - 1] == WETH, 'UniswapV2Router: INVALID_PATH');
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amountIn
);
_swapSupportingFeeOnTransferTokens(path, address(this));
uint amountOut = IERC20(WETH).balanceOf(address(this));
require(amountOut >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
IWETH(WETH).withdraw(amountOut);
TransferHelper.safeTransferETH(to, amountOut);
}
సాధారణ టోకెన్ల కోసం ఉపయోగించే అవే వేరియంట్లు ఇవి, కానీ అవి దానికి బదులుగా _swapSupportingFeeOnTransferTokensని కాల్ చేస్తాయి.
// **** లైబ్రరీ ఫంక్షన్లు ****
function quote(uint amountA, uint reserveA, uint reserveB) public pure virtual override returns (uint amountB) {
return UniswapV2Library.quote(amountA, reserveA, reserveB);
}
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountOut)
{
return UniswapV2Library.getAmountOut(amountIn, reserveIn, reserveOut);
}
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut)
public
pure
virtual
override
returns (uint amountIn)
{
return UniswapV2Library.getAmountIn(amountOut, reserveIn, reserveOut);
}
function getAmountsOut(uint amountIn, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsOut(factory, amountIn, path);
}
function getAmountsIn(uint amountOut, address[] memory path)
public
view
virtual
override
returns (uint[] memory amounts)
{
return UniswapV2Library.getAmountsIn(factory, amountOut, path);
}
}
ఈ ఫంక్షన్లు కేవలం UniswapV2Library ఫంక్షన్లను కాల్ చేసే ప్రాక్సీలు.
UniswapV2Migrator.sol
పాత v1 నుండి v2కి ఎక్స్ఛేంజ్లను మైగ్రేట్ చేయడానికి ఈ కాంట్రాక్ట్ ఉపయోగించబడింది. ఇప్పుడు అవి మైగ్రేట్ చేయబడినందున, ఇది ఇకపై సంబంధితమైనది కాదు.
లైబ్రరీలు
SafeMath లైబ్రరీ (opens in a new tab) బాగా డాక్యుమెంట్ చేయబడింది, కాబట్టి దాన్ని ఇక్కడ డాక్యుమెంట్ చేయాల్సిన అవసరం లేదు.
Math
ఈ లైబ్రరీలో సాధారణంగా Solidity కోడ్లో అవసరం లేని కొన్ని గణిత ఫంక్షన్లు ఉంటాయి, కాబట్టి అవి ఆ భాషలో భాగం కావు.
pragma solidity =0.5.16;
// వివిధ గణిత ఆపరేషన్లను నిర్వహించడానికి ఒక లైబ్రరీ
library Math {
function min(uint x, uint y) internal pure returns (uint z) {
z = x < y ? x : y;
}
// బాబిలోనియన్ పద్ధతి (https://wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
function sqrt(uint y) internal pure returns (uint z) {
if (y > 3) {
z = y;
uint x = y / 2 + 1;
వర్గమూలం (square root) కంటే ఎక్కువగా ఉండే అంచనాగా x తో ప్రారంభించండి (అందుకే మనం 1-3 ని ప్రత్యేక సందర్భాలుగా పరిగణించాలి).
while (x < z) {
z = x;
x = (y / x + x) / 2;
మరింత దగ్గరి అంచనాను పొందండి, మునుపటి అంచనా మరియు మనం కనుగొనడానికి ప్రయత్నిస్తున్న వర్గమూలం సంఖ్యను మునుపటి అంచనాతో భాగించగా వచ్చే సగటు. కొత్త అంచనా ఇప్పటికే ఉన్న దానికంటే తక్కువగా ఉండనంత వరకు పునరావృతం చేయండి. మరిన్ని వివరాల కోసం, ఇక్కడ చూడండి (opens in a new tab).
}
} else if (y != 0) {
z = 1;
మనకు సున్నా యొక్క వర్గమూలం ఎప్పుడూ అవసరం ఉండదు. ఒకటి, రెండు మరియు మూడు యొక్క వర్గమూలాలు దాదాపు ఒకటిగా ఉంటాయి (మనం పూర్ణాంకాలను ఉపయోగిస్తాము, కాబట్టి భిన్నాన్ని విస్మరిస్తాము).
}
}
}
ఫిక్స్డ్ పాయింట్ ఫ్రాక్షన్స్ (UQ112x112)
ఈ లైబ్రరీ భిన్నాలను (fractions) నిర్వహిస్తుంది, ఇవి సాధారణంగా ఎథీరియం అంకగణితంలో భాగం కావు. ఇది x సంఖ్యను x*2^112 గా ఎన్కోడ్ చేయడం ద్వారా దీన్ని చేస్తుంది. ఇది అసలు కూడిక మరియు తీసివేత ఆప్కోడ్లను ఎలాంటి మార్పు లేకుండా ఉపయోగించడానికి అనుమతిస్తుంది.
pragma solidity =0.5.16;
// బైనరీ ఫిక్స్డ్ పాయింట్ నంబర్లను నిర్వహించడానికి ఒక లైబ్రరీ (https://wikipedia.org/wiki/Q_(number_format))
// పరిధి: [0, 2**112 - 1]
// రిజల్యూషన్: 1 / 2**112
library UQ112x112 {
uint224 constant Q112 = 2**112;
Q112 అనేది ఒకటికి ఎన్కోడింగ్.
// uint112 ని UQ112x112 గా ఎన్కోడ్ చేయండి
function encode(uint112 y) internal pure returns (uint224 z) {
z = uint224(y) * Q112; // ఎప్పుడూ ఓవర్ఫ్లో కాదు
}
y అనేది uint112 కాబట్టి, అది గరిష్టంగా 2^112-1 కావచ్చు. ఆ సంఖ్యను ఇప్పటికీ UQ112x112 గా ఎన్కోడ్ చేయవచ్చు.
// UQ112x112 ని uint112 తో భాగించి, UQ112x112 ని తిరిగి ఇస్తుంది
function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
z = x / uint224(y);
}
}
మనం రెండు UQ112x112 విలువలను భాగిస్తే, ఫలితం ఇకపై 2^112 తో గుణించబడదు. కాబట్టి దానికి బదులుగా మనం హారం (denominator) కోసం ఒక పూర్ణాంకాన్ని తీసుకుంటాము. గుణకారం చేయడానికి మనం ఇలాంటి ట్రిక్నే ఉపయోగించాల్సి వచ్చేది, కానీ మనం UQ112x112 విలువల గుణకారం చేయాల్సిన అవసరం లేదు.
UniswapV2Library
ఈ లైబ్రరీ పెరిఫెరీ కాంట్రాక్ట్ల ద్వారా మాత్రమే ఉపయోగించబడుతుంది
pragma solidity >=0.5.0;
import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol';
import "./SafeMath.sol";
library UniswapV2Library {
using SafeMath for uint;
// క్రమబద్ధీకరించబడిన టోకెన్ చిరునామాలను తిరిగి ఇస్తుంది, ఈ క్రమంలో క్రమబద్ధీకరించబడిన పెయిర్ల నుండి రిటర్న్ విలువలను నిర్వహించడానికి ఉపయోగించబడుతుంది
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
require(tokenA != tokenB, 'UniswapV2Library: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'UniswapV2Library: ZERO_ADDRESS');
}
రెండు టోకెన్లను చిరునామా ద్వారా క్రమబద్ధీకరించండి, తద్వారా వాటి కోసం పెయిర్ ఎక్స్ఛేంజ్ చిరునామాను మనం పొందగలుగుతాము. ఇది అవసరం ఎందుకంటే లేకపోతే మనకు రెండు అవకాశాలు ఉంటాయి, ఒకటి A,B పారామితుల కోసం మరియు మరొకటి B,A పారామితుల కోసం, ఇది ఒకదానికి బదులుగా రెండు ఎక్స్ఛేంజ్లకు దారి తీస్తుంది.
// ఎలాంటి బాహ్య కాల్స్ చేయకుండా పెయిర్ కోసం CREATE2 చిరునామాను లెక్కిస్తుంది
function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // ఇనిట్ కోడ్ హాష్
))));
}
ఈ ఫంక్షన్ రెండు టోకెన్ల కోసం పెయిర్ ఎక్స్ఛేంజ్ చిరునామాను గణిస్తుంది. ఈ కాంట్రాక్ట్ CREATE2 ఆప్కోడ్ (opens in a new tab) ఉపయోగించి సృష్టించబడింది, కాబట్టి అది ఉపయోగించే పారామితులు మనకు తెలిస్తే అదే అల్గారిథమ్ని ఉపయోగించి మనం చిరునామాను లెక్కించవచ్చు. ఫ్యాక్టరీని అడగడం కంటే ఇది చాలా చౌకైనది, మరియు
// పెయిర్ కోసం రిజర్వ్లను పొందుతుంది మరియు క్రమబద్ధీకరిస్తుంది
function getReserves(address factory, address tokenA, address tokenB) internal view returns (uint reserveA, uint reserveB) {
(address token0,) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1,) = IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
ఈ ఫంక్షన్ పెయిర్ ఎక్స్ఛేంజ్ కలిగి ఉన్న రెండు టోకెన్ల నిల్వలను (reserves) అందిస్తుంది. ఇది టోకెన్లను ఏ క్రమంలోనైనా స్వీకరించగలదని మరియు అంతర్గత ఉపయోగం కోసం వాటిని క్రమబద్ధీకరిస్తుందని గమనించండి.
// కొంత ఆస్తి మొత్తం మరియు పెయిర్ రిజర్వ్లు ఇచ్చినప్పుడు, ఇతర ఆస్తికి సమానమైన మొత్తాన్ని తిరిగి ఇస్తుంది
function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
amountB = amountA.mul(reserveB) / reserveA;
}
ఎలాంటి రుసుము లేనట్లయితే టోకెన్ A కి బదులుగా మీరు పొందే టోకెన్ B మొత్తాన్ని ఈ ఫంక్షన్ మీకు ఇస్తుంది. బదిలీ అనేది మారకపు రేటును మారుస్తుందని ఈ గణన పరిగణనలోకి తీసుకుంటుంది.
// ఆస్తి యొక్క ఇన్పుట్ మొత్తం మరియు పెయిర్ రిజర్వ్లు ఇచ్చినప్పుడు, ఇతర ఆస్తి యొక్క గరిష్ట అవుట్పుట్ మొత్తాన్ని తిరిగి ఇస్తుంది
function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
పెయిర్ ఎక్స్ఛేంజ్ని ఉపయోగించడానికి ఎలాంటి రుసుము లేనట్లయితే పైన ఉన్న quote ఫంక్షన్ అద్భుతంగా పనిచేస్తుంది. అయితే, 0.3% ఎక్స్ఛేంజ్ రుసుము ఉంటే మీరు వాస్తవంగా పొందే మొత్తం తక్కువగా ఉంటుంది. ఈ ఫంక్షన్ ఎక్స్ఛేంజ్ రుసుము తర్వాత మొత్తాన్ని గణిస్తుంది.
require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint amountInWithFee = amountIn.mul(997);
uint numerator = amountInWithFee.mul(reserveOut);
uint denominator = reserveIn.mul(1000).add(amountInWithFee);
amountOut = numerator / denominator;
}
Solidity స్థానికంగా భిన్నాలను నిర్వహించదు, కాబట్టి మనం మొత్తాన్ని 0.997 తో గుణించలేము. దానికి బదులుగా, మనం లవాన్ని (numerator) 997 తో మరియు హారాన్ని (denominator) 1000 తో గుణిస్తాము, తద్వారా అదే ప్రభావాన్ని సాధిస్తాము.
// ఆస్తి యొక్క అవుట్పుట్ మొత్తం మరియు పెయిర్ రిజర్వ్లు ఇచ్చినప్పుడు, ఇతర ఆస్తికి అవసరమైన ఇన్పుట్ మొత్తాన్ని తిరిగి ఇస్తుంది
function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) internal pure returns (uint amountIn) {
require(amountOut > 0, 'UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
uint numerator = reserveIn.mul(amountOut).mul(1000);
uint denominator = reserveOut.sub(amountOut).mul(997);
amountIn = (numerator / denominator).add(1);
}
ఈ ఫంక్షన్ దాదాపు అదే పని చేస్తుంది, కానీ ఇది అవుట్పుట్ మొత్తాన్ని పొందుతుంది మరియు ఇన్పుట్ను అందిస్తుంది.
// ఎన్ని పెయిర్లపైనైనా చైన్డ్ getAmountOut లెక్కలను నిర్వహిస్తుంది
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
// ఎన్ని పెయిర్లపైనైనా చైన్డ్ getAmountIn లెక్కలను నిర్వహిస్తుంది
function getAmountsIn(address factory, uint amountOut, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[amounts.length - 1] = amountOut;
for (uint i = path.length - 1; i > 0; i--) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i - 1], path[i]);
amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
}
}
}
అనేక పెయిర్ ఎక్స్ఛేంజ్ల ద్వారా వెళ్లాల్సిన అవసరం వచ్చినప్పుడు విలువలను గుర్తించడాన్ని ఈ రెండు ఫంక్షన్లు నిర్వహిస్తాయి.
ట్రాన్స్ఫర్ హెల్పర్
రివర్ట్ మరియు false విలువ రిటర్న్ను ఒకే విధంగా పరిగణించడానికి ఈ లైబ్రరీ (opens in a new tab) ERC-20 మరియు ఎథీరియం బదిలీల చుట్టూ విజయ తనిఖీలను (success checks) జోడిస్తుంది.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.0;
// స్థిరంగా true/false తిరిగి ఇవ్వని ERC20 టోకెన్లతో ఇంటరాక్ట్ అవ్వడానికి మరియు ETH పంపడానికి సహాయక పద్ధతులు
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
మనం వేరొక కాంట్రాక్ట్ను రెండు మార్గాలలో ఒకదాని ద్వారా కాల్ చేయవచ్చు:
- ఫంక్షన్ కాల్ని సృష్టించడానికి ఇంటర్ఫేస్ నిర్వచనాన్ని ఉపయోగించడం
- కాల్ని సృష్టించడానికి అప్లికేషన్ బైనరీ ఇంటర్ఫేస్ (ABI) (opens in a new tab) ని "మాన్యువల్గా" ఉపయోగించడం. కోడ్ రచయిత దీన్నే చేయాలని నిర్ణయించుకున్నారు.
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeApprove: approve failed'
);
}
ERC-20 ప్రమాణానికి ముందు సృష్టించబడిన టోకెన్తో బ్యాక్వర్డ్స్ కంపాటిబిలిటీ కోసం, ERC-20 కాల్ రివర్ట్ అవ్వడం ద్వారా (ఈ సందర్భంలో success అనేది false అవుతుంది) లేదా విజయవంతమై false విలువను అందించడం ద్వారా (ఈ సందర్భంలో అవుట్పుట్ డేటా ఉంటుంది, మరియు మీరు దాన్ని బూలియన్గా డీకోడ్ చేస్తే మీకు false వస్తుంది) విఫలం కావచ్చు.
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeTransfer: transfer failed'
);
}
ఈ ఫంక్షన్ ERC-20 యొక్క బదిలీ కార్యాచరణను (opens in a new tab) అమలు చేస్తుంది, ఇది వేరొక ఖాతా అందించిన అనుమతి మొత్తాన్ని ఖర్చు చేయడానికి ఒక ఖాతాను అనుమతిస్తుంది.
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::transferFrom: transferFrom failed'
);
}
ఈ ఫంక్షన్ ERC-20 యొక్క transferFrom కార్యాచరణను (opens in a new tab) అమలు చేస్తుంది, ఇది వేరొక ఖాతా అందించిన అనుమతి మొత్తాన్ని ఖర్చు చేయడానికి ఒక ఖాతాను అనుమతిస్తుంది.
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
}
ఈ ఫంక్షన్ ఈథర్ను ఒక ఖాతాకు బదిలీ చేస్తుంది. వేరొక కాంట్రాక్ట్కు చేసే ఏ కాల్ అయినా ఈథర్ను పంపడానికి ప్రయత్నించవచ్చు. మనం వాస్తవానికి ఏ ఫంక్షన్ను కాల్ చేయాల్సిన అవసరం లేదు కాబట్టి, మనం కాల్తో పాటు ఎలాంటి డేటాను పంపము.
ముగింపు
ఇది సుమారు 50 పేజీల సుదీర్ఘమైన వ్యాసం. మీరు ఇక్కడి వరకు చదివినట్లయితే, అభినందనలు! చిన్న నమూనా ప్రోగ్రామ్లకు భిన్నంగా, వాస్తవ-ప్రపంచ అప్లికేషన్ను రాయడంలో పరిగణించాల్సిన అంశాలను మీరు ఇప్పటికల్లా అర్థం చేసుకున్నారని మరియు మీ స్వంత అవసరాల కోసం కాంట్రాక్ట్లను మరింత మెరుగ్గా రాయగలుగుతారని ఆశిస్తున్నాను.
ఇప్పుడు వెళ్లి ఏదైనా ఉపయోగకరమైనదాన్ని రాసి మమ్మల్ని ఆశ్చర్యపరచండి.
నా మరిన్ని పనుల కోసం ఇక్కడ చూడండి (opens in a new tab).
పేజీ చివరి నవీకరణ: 3 ఏప్రిల్, 2026
