Ruka kwenda kwenye maudhui makuu

Muongozo wa Mkataba wa Uniswap-v2

uimara
Intermediate
Ori Pomerantz
1 Mei 2021
57 minute read

Utangulizi

Uniswap v2 (opens in a new tab) inaweza kuunda soko la kubadilishana kati ya tokeni zozote mbili za ERC-20. Katika makala haya tutapitia msimbo chanzo wa mikataba inayotekeleza itifaki hii na kuona kwa nini imeandikwa kwa njia hii.

Uniswap Inafanya Nini?

Kimsingi, kuna aina mbili za watumiaji: watoa huduma za ukwasi na wafanyabiashara.

Watoa huduma za ukwasi huweka kwenye bwawa tokeni mbili ambazo zinaweza kubadilishwa (tutaziita Token0 na Token1). Kwa malipo, wanapokea tokeni ya tatu inayowakilisha umiliki wa sehemu ya bwawa inayoitwa tokeni ya ukwasi.

Wafanyabiashara hutuma aina moja ya tokeni kwenye bwawa na kupokea nyingine (kwa mfano, tuma Token0 na pokea Token1) kutoka kwenye bwawa lililotolewa na watoa huduma za ukwasi. Kiwango cha ubadilishaji huamuliwa na idadi linganishi ya Token0 na Token1 ambazo bwawa linazo. Kwa kuongezea, bwawa huchukua asilimia ndogo kama tuzo kwa bwawa la ukwasi.

Watoa huduma za ukwasi wanapotaka mali zao zirudishwe wanaweza kuondoa tokeni za bwawa na kupokea tokeni zao, ikijumuisha sehemu yao ya tuzo.

Bofya hapa kwa maelezo kamili (opens in a new tab).

Kwa nini v2? Kwa nini si v3?

Uniswap v3 (opens in a new tab) ni boresho ambalo ni changamano zaidi kuliko v2. Ni rahisi kujifunza v2 kwanza na kisha kwenda v3.

Mikataba ya Msingi dhidi ya Mikataba ya Pembeni

Uniswap v2 imegawanywa katika sehemu mbili, msingi na pembeni. Mgawanyiko huu unaruhusu mikataba ya msingi, ambayo inashikilia mali na kwa hivyo lazima iwe salama, kuwa rahisi na rahisi kukaguliwa. Utendaji wote wa ziada unaohitajika na wafanyabiashara unaweza kutolewa na mikataba ya pembeni.

Mtiririko wa Data na Udhibiti

Huu ndio mtiririko wa data na udhibiti unaotokea unapofanya vitendo vikuu vitatu vya Uniswap:

  1. Kubadilisha kati ya tokeni tofauti
  2. Ongeza ukwasi kwenye soko na uzawadiwe kwa tokeni za ukwasi za ERC-20 za ubadilishaji wa jozi
  3. Ondoa tokeni za ukwasi za ERC-20 na urejeshewe tokeni za ERC-20 ambazo ubadilishaji wa jozi unaruhusu wafanyabiashara kubadilishana

Badilisha

Huu ndio mtiririko wa kawaida zaidi, unaotumiwa na wafanyabiashara:

Mwitaji

  1. Ipe akaunti ya pembeni ruhusa ya kiasi kitakachobadilishwa.
  2. Piga simu mojawapo ya kazi nyingi za kubadilisha za mkataba wa pembeni (ipi inategemea kama ETH inahusika au la, kama mfanyabiashara anabainisha kiasi cha tokeni za kuweka au kiasi cha tokeni za kurejeshewa, n.k). Kila kazi ya kubadilisha inakubali path, safu ya mabadilishano ya kupitia.

Katika mkataba wa pembeni (UniswapV2Router02.sol)

  1. Tambua kiasi kinachohitaji kufanyiwa biashara kwenye kila ubadilishaji kando ya njia.
  2. Inarudia juu ya njia. Kwa kila ubadilishaji njiani inatuma tokeni ya kuingiza na kisha inapiga simu kazi ya swap ya ubadilishaji. Katika hali nyingi anwani lengwa ya tokeni ni ubadilishaji wa jozi unaofuata kwenye njia. Katika ubadilishaji wa mwisho ni anwani iliyotolewa na mfanyabiashara.

Katika mkataba wa msingi (UniswapV2Pair.sol) {#in-the-core-contract-uniswapv2pairsol-2}5. Thibitisha kuwa mkataba wa msingi haufanyiwi udanganyifu na unaweza kudumisha ukwasi wa kutosha baada ya ubadilishaji.

  1. Angalia ni tokeni ngapi za ziada tunazo pamoja na akiba zinazojulikana. Kiasi hicho ni idadi ya tokeni za kuingiza tulizopokea ili kubadilisha.
  2. Tuma tokeni za matokeo kwenda lengwa.
  3. Piga simu _update ili kusasisha kiasi cha akiba

Rudi kwenye mkataba wa pembeni (UniswapV2Router02.sol)

  1. Fanya usafi wowote unaohitajika (kwa mfano, ondoa tokeni za WETH ili kurudisha ETH kumtumia mfanyabiashara)

Ongeza Ukwasi

Mwitaji

  1. Ipe akaunti ya pembeni ruhusa ya kiasi kitakachoongezwa kwenye bwawa la ukwasi.
  2. Piga simu mojawapo ya kazi za addLiquidity za mkataba wa pembeni.

Katika mkataba wa pembeni (UniswapV2Router02.sol)

  1. Unda ubadilishaji mpya wa jozi ikiwa ni lazima
  2. Ikiwa kuna ubadilishaji wa jozi uliopo, hesabu kiasi cha tokeni za kuongeza. Hii inatakiwa kuwa na thamani sawa kwa tokeni zote mbili, kwa hiyo uwiano sawa wa tokeni mpya kwa tokeni zilizopo.
  3. Angalia kama kiasi kinakubalika (wapigaji simu wanaweza kubainisha kiasi cha chini ambacho hawapendi kuongeza ukwasi chini yake)
  4. Piga simu mkataba wa msingi.

Katika mkataba wa msingi (UniswapV2Pair.sol)

  1. Zalisha tokeni za ukwasi na uzitumie kwa mwitaji
  2. Piga simu _update ili kusasisha kiasi cha akiba

Ondoa Ukwasi

Mwitaji

  1. Ipe akaunti ya pembeni ruhusa ya tokeni za ukwasi zitakazoondolewa badala ya tokeni za msingi.
  2. Piga simu mojawapo ya kazi za removeLiquidity za mkataba wa pembeni.

Katika mkataba wa pembeni (UniswapV2Router02.sol)

  1. Tuma tokeni za ukwasi kwenye ubadilishaji wa jozi

Katika mkataba wa msingi (UniswapV2Pair.sol)

  1. Tuma kwenye anwani lengwa tokeni za msingi kulingana na tokeni zilizoondolewa. Kwa mfano ikiwa kuna tokeni 1000 za A kwenye bwawa, tokeni 500 za B, na tokeni 90 za ukwasi, na tunapokea tokeni 9 za kuondoa, tunaondoa 10% ya tokeni za ukwasi kwa hiyo tunamrudishia mtumiaji tokeni 100 za A na tokeni 50 za B.
  2. Ondoa tokeni za ukwasi
  3. Piga simu _update ili kusasisha kiasi cha akiba

Mikataba ya Msingi

Hii ni mikataba salama ambayo inashikilia ukwasi.

UniswapV2Pair.sol

Mkataba huu (opens in a new tab) unatekeleza bwawa halisi linalobadilisha tokeni. Ni utendaji mkuu wa 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';
Onyesha yote

Hizi zote ni miingiliano ambayo mkataba unahitaji kujua kuihusu, ama kwa sababu mkataba unazitekeleza (IUniswapV2Pair na UniswapV2ERC20) au kwa sababu inapiga simu mikataba inayozitekeleza.

1contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {

Mkataba huu unarithi kutoka UniswapV2ERC20, ambayo hutoa kazi za ERC-20 kwa tokeni za ukwasi.

1 using SafeMath for uint;

Maktaba ya SafeMath library (opens in a new tab) inatumika kuepuka kufurika na kupungua. Hii ni muhimu kwa sababu vinginevyo tunaweza kuishia na hali ambapo thamani inapaswa kuwa -1, lakini badala yake ni 2^256-1.

1 using UQ112x112 for uint224;

Mahesabu mengi katika mkataba wa bwawa yanahitaji sehemu. Hata hivyo, sehemu hazitumiki na EVM. Suluhisho ambalo Uniswap ilipata ni kutumia thamani za biti 224, na biti 112 kwa sehemu ya nambari kamili, na biti 112 kwa sehemu. Kwa hivyo 1.0 inawakilishwa kama 2^112, 1.5 inawakilishwa kama 2^112 + 2^111, n.k.

Maelezo zaidi kuhusu maktaba hii yanapatikana baadaye kwenye hati.

Vigezo

1 uint public constant MINIMUM_LIQUIDITY = 10**3;

Ili kuepuka visa vya kugawanya kwa sifuri, kuna idadi ya chini ya tokeni za ukwasi ambazo zipo kila wakati (lakini zinamilikiwa na akaunti sifuri). Nambari hiyo ni MINIMUM_LIQUIDITY, elfu moja.

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

Hiki ni kiteuzi cha ABI kwa ajili ya kazi ya uhamisho wa ERC-20. Inatumika kuhamisha tokeni za ERC-20 katika akaunti mbili za tokeni.

1 address public factory;

Huu ni mkataba wa kiwanda uliounda bwawa hili. Kila bwawa ni ubadilishanaji kati ya tokeni mbili za ERC-20, kiwanda ni sehemu kuu inayounganisha mabwawa haya yote.

1 address public token0;
2 address public token1;

Kuna anwani za mikataba ya aina mbili za tokeni za ERC-20 zinazoweza kubadilishwa na bwawa hili.

1 uint112 private reserve0; // hutumia nafasi moja ya kuhifadhi, inayopatikana kupitia getReserves
2 uint112 private reserve1; // hutumia nafasi moja ya kuhifadhi, inayopatikana kupitia getReserves

Akiba ambayo bwawa lina nayo kwa kila aina ya tokeni. Tunadhania kuwa zote mbili zinawakilisha kiasi sawa cha thamani, na kwa hivyo kila token0 ina thamani ya reserve1/reserve0 ya tokeni1.

1 uint32 private blockTimestampLast; // hutumia nafasi moja ya kuhifadhi, inayopatikana kupitia getReserves

Mhuri wa muda wa kizuizi cha mwisho ambapo ubadilishanaji ulitokea, unaotumika kufuatilia viwango vya ubadilishanaji kwa muda.

Moja ya gharama kubwa zaidi za gesi za mikataba ya Ethereum ni hifadhi, ambayo huendelea kutoka simu moja ya mkataba hadi nyingine. Kila seli ya hifadhi ina urefu wa biti 256. Kwa hivyo vigezo vitatu, reserve0, reserve1, na blockTimestampLast, vimepangwa kwa namna ambayo thamani moja ya hifadhi inaweza kujumuisha zote tatu (112+112+32=256).

1 uint public price0CumulativeLast;
2 uint public price1CumulativeLast;

Vigezo hivi hushikilia gharama limbikizi kwa kila tokeni (kila moja kwa mujibu wa nyingine). Vinaweza kutumika kuhesabu kiwango cha wastani cha ubadilishanaji kwa kipindi cha muda.

1 uint public kLast; // reserve0 * reserve1, kuanzia mara tu baada ya tukio la hivi karibuni la ukwasi

Njia ambayo ubadilishanaji wa jozi huamua kiwango cha ubadilishanaji kati ya token0 na token1 ni kuweka zidishi la akiba mbili kuwa la kudumu wakati wa biashara. kLast ni thamani hii. Inabadilika wakati mtoa huduma wa ukwasi anaweka au kutoa tokeni, na huongezeka kidogo kwa sababu ya ada ya soko ya 0.3%.

Huu ni mfano rahisi. Kumbuka kuwa kwa ajili ya kurahisisha, jedwali lina tarakimu tatu tu baada ya nukta ya desimali, na tunapuuza ada ya biashara ya 0.3% kwa hivyo nambari si sahihi.

Tukioreserve0reserve1reserve0 * reserve1Kiwango cha wastani cha ubadilishaji (token1 / token0)
Usanidi wa awali1,000.0001,000.0001,000,000
Mfanyabiashara A anabadilisha tokeni 50 za token0 kwa tokeni 47.619 za token11,050.000952.3811,000,0000.952
Mfanyabiashara B anabadilisha tokeni 10 za token0 kwa tokeni 8.984 za token11,060.000943.3961,000,0000.898
Mfanyabiashara C anabadilisha tokeni 40 za token0 kwa tokeni 34.305 za token11,100.000909.0901,000,0000.858
Mfanyabiashara D anabadilisha tokeni 100 za token1 kwa tokeni 109.01 za token0990.9901,009.0901,000,0000.917
Mfanyabiashara E anabadilisha tokeni 10 za token0 kwa tokeni 10.079 za token11,000.990999.0101,000,0001.008

Wafanyabiashara wanapotoa token0 zaidi, thamani linganishi ya token1 huongezeka, na kinyume chake, kulingana na usambazaji na mahitaji.

Funga

1 uint private unlocked = 1;

Kuna aina ya udhaifu wa usalama unaotokana na matumizi mabaya ya kuingia tena (opens in a new tab). Uniswap inahitaji kuhamisha tokeni za kiholela za ERC-20, ambayo inamaanisha kupiga simu mikataba ya ERC-20 ambayo inaweza kujaribu kutumia vibaya soko la Uniswap linaloziita. Kwa kuwa na kigezo cha unlocked kama sehemu ya mkataba, tunaweza kuzuia kazi zisiitwe wakati zinaendeshwa (ndani ya muamala mmoja).

1 modifier lock() {

Kazi hii ni kirekebishaji (opens in a new tab), kazi inayozunguka kazi ya kawaida ili kubadilisha tabia yake kwa namna fulani.

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

Ikiwa unlocked ni sawa na moja, iweke sifuri. Ikiwa tayari ni sifuri, rudisha simu, ifanye ishindwe.

1 _;

Katika kirekebishaji _; ni simu ya awali ya kazi (pamoja na vigezo vyote). Hapa inamaanisha kuwa simu ya kazi inatokea tu ikiwa unlocked ilikuwa moja wakati ilipoitwa, na wakati inaendeshwa thamani ya unlocked ni sifuri.

1 unlocked = 1;
2 }

Baada ya kazi kuu kurudi, toa kufuli.

Mbalimbali kazi

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

Kazi hii huwapa wapigaji simu hali ya sasa ya ubadilishanaji. Ona kuwa kazi za Solidity zinaweza kurudisha thamani nyingi (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));

Kazi hii ya ndani huhamisha kiasi cha tokeni za ERC20 kutoka kwenye ubadilishanaji kwenda kwa mtu mwingine. SELECTOR inabainisha kuwa kazi tunayoita ni transfer(address,uint) (tazama ufafanuzi hapo juu).

Ili kuepuka kulazimika kuingiza kiolesura cha kazi ya tokeni, tunatengeneza simu "kwa mikono" kwa kutumia moja ya kazi za ABI (opens in a new tab).

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

Kuna njia mbili ambazo simu ya uhamisho ya ERC-20 inaweza kuripoti kushindwa:

  1. Rejesha. Ikiwa simu ya mkataba wa nje inarejeshwa, basi thamani ya kurudi ya boolean ni false
  2. Maliza kawaida lakini ripoti kushindwa. Katika hali hiyo bafa ya thamani ya kurudi ina urefu usio wa sifuri, na inapopambanuliwa kama thamani ya boolean ni false

Ikiwa mojawapo ya masharti haya yatatokea, rejesha.

Matukio

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

Matukio haya mawili hutolewa wakati mtoa huduma wa ukwasi anapoweka ukwasi (Mint) au anapoutoa (Burn). Katika hali zote mbili, kiasi cha token0 na token1 kinachowekwa au kutolewa ni sehemu ya tukio, pamoja na utambulisho wa akaunti iliyotuita (sender). Katika kesi ya uondoaji, tukio pia linajumuisha lengo lililopokea tokeni (to), ambalo linaweza lisiwe sawa na mtumaji.

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

Tukio hili hutolewa wakati mfanyabiashara anapobadilisha tokeni moja kwa nyingine. Tena, mtumaji na lengo huenda wasiwe sawa. Kila tokeni inaweza kutumwa kwenye ubadilishanaji, au kupokelewa kutoka kwake.

1 event Sync(uint112 reserve0, uint112 reserve1);

Mwishowe, Sync hutolewa kila wakati tokeni zinapoongezwa au kutolewa, bila kujali sababu, ili kutoa taarifa za hivi karibuni za akiba (na kwa hivyo kiwango cha ubadilishanaji).

Kazi za Usanidi

Kazi hizi zinapaswa kuitwa mara moja wakati ubadilishanaji mpya wa jozi unapowekwa.

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

Kiunda huhakikisha tutafuatilia anwani ya kiwanda kilichounda jozi. Taarifa hii inahitajika kwa initialize na kwa ada ya kiwanda (ikiwa ipo)

1 // inaitwa mara moja na kiwanda wakati wa uwekaji
2 function initialize(address _token0, address _token1) external {
3 require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // ukaguzi wa kutosha
4 token0 = _token0;
5 token1 = _token1;
6 }

Kazi hii inaruhusu kiwanda (na kiwanda pekee) kubainisha tokeni mbili za ERC-20 ambazo jozi hii itabadilishana.

Kazi za Kusasisha za Ndani

_update
1 // sasisha akiba na, kwa simu ya kwanza kwa kila kizuizi, vikusanya bei
2 function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {

Kazi hii inaitwa kila wakati tokeni zinapowekwa au kutolewa.

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

Ikiwa ama balance0 au balance1 (uint256) ni ya juu kuliko uint112(-1) (=2^112-1) (hivyo inafurika na kurudi nyuma hadi 0 inapobadilishwa kuwa uint112) kataa kuendelea na _update ili kuzuia kufurika. Kwa tokeni ya kawaida inayoweza kugawanywa katika vitengo 10^18, hii inamaanisha kila ubadilishanaji unazuiliwa kwa takriban 5.1*10^15 ya kila tokeni. Hadi sasa hilo halijakuwa tatizo.

1 uint32 blockTimestamp = uint32(block.timestamp % 2**32);
2 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // kufurika kunatakikana
3 if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {

Ikiwa muda uliopita sio sifuri, inamaanisha sisi ni muamala wa kwanza wa ubadilishanaji kwenye kizuizi hiki. Katika hali hiyo, tunahitaji kusasisha vikusanya gharama.

1 // * haifuriki kamwe, na + kufurika kunatakikana
2 price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
3 price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
4 }

Kila kikusanya gharama husasishwa na gharama ya hivi karibuni (akiba ya tokeni nyingine/akiba ya tokeni hii) mara muda uliopita kwa sekunde. Ili kupata bei ya wastani, unasoma bei limbikizi katika pointi mbili kwa wakati na kugawanya kwa tofauti ya muda kati yao. Kwa mfano, fikiria mfuatano huu wa matukio:

Tukioreserve0reserve1muhuri wa mudaKiwango cha ubadilishaji cha ukingoni (reserve1 / reserve0)price0CumulativeLast
Usanidi wa awali1,000.0001,000.0005,0001.0000
Mfanyabiashara A anaweka tokeni 50 za token0 na anapata tokeni 47.619 za token11,050.000952.3815,0200.90720
Mfanyabiashara B anaweka tokeni 10 za token0 na anapata tokeni 8.984 za token11,060.000943.3965,0300.89020+10*0.907 = 29.07
Mfanyabiashara C anaweka tokeni 40 za token0 na anapata tokeni 34.305 za token11,100.000909.0905,1000.82629.07+70*0.890 = 91.37
Mfanyabiashara D anaweka tokeni 100 za token1 na anapata tokeni 109.01 za token0990.9901,009.0905,1101.01891.37+10*0.826 = 99.63
Mfanyabiashara E anaweka tokeni 10 za token0 na anapata tokeni 10.079 za token11,000.990999.0105,1500.99899.63+40*1.1018 = 143.702

Tuseme tunataka kuhesabu bei ya wastani ya Token0 kati ya mihuri ya muda 5,030 na 5,150. Tofauti katika thamani ya price0Cumulative ni 143.702-29.07=114.632. Hii ni wastani kwa dakika mbili (sekunde 120). Kwa hivyo bei ya wastani ni 114.632/120 = 0.955.

Uhesabuji huu wa bei ndiyo sababu tunahitaji kujua ukubwa wa zamani wa akiba.

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

Mwishowe, sasisha vigezo vya kimataifa na toa tukio la Sync.

_mintFee
1 // ikiwa ada imewashwa, zalisha ukwasi sawa na 1/6 ya ukuaji katika sqrt(k)
2 function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {

Katika Uniswap 2.0 wafanyabiashara hulipa ada ya 0.30% kutumia soko. Sehemu kubwa ya ada hiyo (0.25% ya biashara) huenda kwa watoa huduma za ukwasi. Asilimia 0.05 iliyobaki inaweza kwenda kwa watoa huduma za ukwasi au kwa anwani iliyobainishwa na kiwanda kama ada ya itifaki, ambayo hulipa Uniswap kwa juhudi zao za maendeleo.

Ili kupunguza mahesabu (na kwa hivyo gharama za gesi), ada hii inahesabiwa tu wakati ukwasi unapoongezwa au kuondolewa kwenye bwawa, badala ya kila muamala.

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

Soma lengo la ada la kiwanda. Ikiwa ni sifuri basi hakuna ada ya itifaki na hakuna haja ya kuhesabu ada hiyo.

1 uint _kLast = kLast; // akiba ya gesi

Kigezo cha hali cha kLast kiko kwenye hifadhi, kwa hivyo kitakuwa na thamani kati ya simu tofauti kwa mkataba. Upatikanaji wa hifadhi ni ghali zaidi kuliko upatikanaji wa kumbukumbu tete inayotolewa wakati simu ya kazi kwa mkataba inapoisha, kwa hivyo tunatumia kigezo cha ndani kuokoa gesi.

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

Watoa huduma za ukwasi wanapata sehemu yao kwa kuongezeka kwa thamani ya tokeni zao za ukwasi. Lakini ada ya itifaki inahitaji tokeni mpya za ukwasi kuzalishwa na kutolewa kwa anwani ya feeTo.

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

Ikiwa kuna ukwasi mpya wa kukusanya ada ya itifaki. Unaweza kuona kazi ya kipeo cha pili baadaye katika makala haya

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

Hesabu hii ngumu ya ada imeelezwa katika waraka rasmi (opens in a new tab) kwenye ukurasa wa 5. Tunajua kuwa kati ya wakati kLast ilipohesabiwa na sasa hakuna ukwasi ulioongezwa au kuondolewa (kwa sababu tunaendesha hesabu hii kila wakati ukwasi unapoongezwa au kuondolewa, kabla haujabadilika), kwa hivyo mabadiliko yoyote katika reserve0 * reserve1 lazima yatoke kwenye ada za muamala (bila hizo tungehifadhi reserve0 * reserve1 kuwa thabiti).

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

Tumia kazi ya UniswapV2ERC20._mint kuunda tokeni za ziada za ukwasi na kuzipa kwa feeTo.

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

Ikiwa hakuna ada weka kLast kuwa sifuri (ikiwa tayari si hivyo). Wakati mkataba huu ulipoandikwa kulikuwa na kipengele cha kurejesha gesi (opens in a new tab) ambacho kilihimiza mikataba kupunguza ukubwa wa jumla wa hali ya Ethereum kwa kuweka sifuri kwenye hifadhi ambayo hawakuihitaji. Msimbo huu unapata urejeshaji huo inapowezekana.

Kazi Zinazoweza Kufikiwa Nje

Kumbuka kuwa ingawa muamala au mkataba wowote unaweza kuita kazi hizi, zimeundwa kuitwa kutoka kwa mkataba wa pembeni. Ukiziita moja kwa moja huwezi kudanganya ubadilishaji wa jozi, lakini unaweza kupoteza thamani kwa kosa.

zalisha
1 // kazi hii ya kiwango cha chini inapaswa kuitwa kutoka kwa mkataba unaofanya ukaguzi muhimu wa usalama
2 function mint(address to) external lock returns (uint liquidity) {

Kazi hii inaitwa wakati mtoa huduma wa ukwasi anapoongeza ukwasi kwenye bwawa. Inazalisha tokeni za ziada za ukwasi kama tuzo. Inapaswa kuitwa kutoka kwa mkataba wa pembeni unaoiita baada ya kuongeza ukwasi katika muamala mmoja (ili hakuna mtu mwingine atakayeweza kuwasilisha muamala unaodai ukwasi mpya kabla ya mmiliki halali).

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // akiba ya gesi

Hii ndiyo njia ya kusoma matokeo ya kazi ya Solidity inayorudisha thamani nyingi. Tunatupa thamani za mwisho zilizorejeshwa, muhuri wa muda wa kizuizi, kwa sababu hatuihitaji.

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

Pata salio za sasa na uone ni kiasi gani kimeongezwa cha kila aina ya tokeni.

1 bool feeOn = _mintFee(_reserve0, _reserve1);

Hesabu ada za itifaki za kukusanya, ikiwa zipo, na uzalishe tokeni za ukwasi ipasavyo. Kwa sababu vigezo vya _mintFee ni thamani za zamani za akiba, ada inahesabiwa kwa usahihi kulingana na mabadiliko ya bwawa kutokana na ada.

1 uint _totalSupply = totalSupply; // akiba ya gesi, lazima ifafanuliwe hapa kwani totalSupply inaweza kusasishwa katika _mintFee
2 if (_totalSupply == 0) {
3 liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
4 _mint(address(0), MINIMUM_LIQUIDITY); // funga kabisa tokeni za kwanza za MINIMUM_LIQUIDITY

Ikiwa hii ni amana ya kwanza, tengeneza tokeni MINIMUM_LIQUIDITY na uzitume kwa anwani sifuri ili kuzifunga. Hazitakombolewa kamwe, ambayo inamaanisha bwawa halitawahi kumwagika kabisa (hii inatuokoa kutokana na kugawanya kwa sifuri katika baadhi ya maeneo). Thamani ya MINIMUM_LIQUIDITY ni elfu moja, ambayo kwa kuzingatia tokeni nyingi za ERC-20 zimegawanywa katika vitengo vya 10^-18 vya tokeni, kama ETH inavyogawanywa kuwa wei, ni 10^-15 kwa thamani ya tokeni moja. Sio gharama kubwa.

Wakati wa amana ya kwanza hatujui thamani linganishi ya tokeni mbili, kwa hivyo tunazidisha kiasi na kuchukua mzizi wa mraba, tukichukulia kwamba amana inatupatia thamani sawa katika tokeni zote mbili.

Tunaweza kuamini hii kwa sababu ni kwa manufaa ya mweka amana kutoa thamani sawa, ili kuepuka kupoteza thamani kwa usuluhishi. Tuseme kwamba thamani ya tokeni mbili ni sawa, lakini mweka amana wetu aliweka mara nne zaidi ya Token1 kuliko Token0. Mfanyabiashara anaweza kutumia ukweli kwamba ubadilishaji wa jozi unafikiri kwamba Token0 ina thamani zaidi ili kutoa thamani kutoka humo.

Tukioreserve0reserve1reserve0 * reserve1Thamani ya bwawa (reserve0 + reserve1)
Usanidi wa awali83225640
Mfanyabiashara anaweka tokeni 8 za Token0, anapata tokeni 16 za Token1161625632

Kama unavyoona, mfanyabiashara alipata tokeni 8 za ziada, ambazo zinatokana na kupungua kwa thamani ya bwawa, na kumuumiza mweka amana anayemiliki.

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

Kwa kila amana inayofuata tayari tunajua kiwango cha ubadilishanaji kati ya mali mbili, na tunatarajia watoa huduma za ukwasi kutoa thamani sawa katika zote mbili. Ikiwa hawafanyi hivyo, tunawapa tokeni za ukwasi kulingana na thamani ndogo waliyotoa kama adhabu.

Iwe ni amana ya awali au inayofuata, idadi ya tokeni za ukwasi tunazotoa ni sawa na mzizi wa mraba wa mabadiliko katika reserve0*reserve1 na thamani ya tokeni ya ukwasi haibadiliki (isipokuwa tupate amana ambayo haina thamani sawa za aina zote mbili, katika hali hiyo "faini" inasambazwa). Huu ni mfano mwingine na tokeni mbili ambazo zina thamani sawa, na amana tatu nzuri na moja mbaya (amana ya aina moja tu ya tokeni, kwa hivyo haitoi tokeni za ukwasi).

Tukioreserve0reserve1reserve0 * reserve1Thamani ya bwawa (reserve0 + reserve1)Tokeni za ukwasi zilizozalishwa kwa amana hiiJumla ya tokeni za ukwasithamani ya kila tokeni ya ukwasi
Usanidi wa awali8.0008.0006416.000882.000
Weka aina nne za kila moja12.00012.00014424.0004122.000
Weka aina mbili za kila moja14.00014.00019628.0002142.000
Amana ya thamani isiyo sawa18.00014.00025232.000014~2.286
Baada ya usuluhishi~15.874~15.874252~31.748014~2.267
1 }
2 require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
3 _mint(to, liquidity);

Tumia kazi ya UniswapV2ERC20._mint kuunda tokeni za ziada za ukwasi na kuzipa kwa akaunti sahihi.

1
2 _update(balance0, balance1, _reserve0, _reserve1);
3 if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 na reserve1 zimesasishwa
4 emit Mint(msg.sender, amount0, amount1);
5 }

Sasisha vigezo vya hali (reserve0, reserve1, na ikihitajika kLast) na toa tukio linalofaa.

ondoa
1 // kazi hii ya kiwango cha chini inapaswa kuitwa kutoka kwa mkataba unaofanya ukaguzi muhimu wa usalama
2 function burn(address to) external lock returns (uint amount0, uint amount1) {

Kazi hii inaitwa wakati ukwasi unapoondolewa na tokeni zinazofaa za ukwasi zinahitaji kuondolewa. Inapaswa pia kuitwa kutoka kwa akaunti ya pembeni.

1 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // akiba ya gesi
2 address _token0 = token0; // akiba ya gesi
3 address _token1 = token1; // akiba ya gesi
4 uint balance0 = IERC20(_token0).balanceOf(address(this));
5 uint balance1 = IERC20(_token1).balanceOf(address(this));
6 uint liquidity = balanceOf[address(this)];

Mkataba wa pembeni ulihamisha ukwasi wa kuondolewa kwenye mkataba huu kabla ya simu. Kwa njia hiyo tunajua ni kiasi gani cha ukwasi cha kuondoa, na tunaweza kuhakikisha kuwa kinaondolewa.

1 bool feeOn = _mintFee(_reserve0, _reserve1);
2 uint _totalSupply = totalSupply; // akiba ya gesi, lazima ifafanuliwe hapa kwani totalSupply inaweza kusasishwa katika _mintFee
3 amount0 = liquidity.mul(balance0) / _totalSupply; // kutumia salio huhakikisha usambazaji wa pro-rata
4 amount1 = liquidity.mul(balance1) / _totalSupply; // kutumia salio huhakikisha usambazaji wa pro-rata
5 require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');

Mtoa huduma wa ukwasi anapokea thamani sawa ya tokeni zote mbili. Kwa njia hii hatubadilishi kiwango cha ubadilishanaji.

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 na reserve1 zimesasishwa
9 emit Burn(msg.sender, amount0, amount1, to);
10 }
11
Onyesha yote

Sehemu iliyobaki ya kazi ya burn ni kioo cha kazi ya mint hapo juu.

badilisha
1 // kazi hii ya kiwango cha chini inapaswa kuitwa kutoka kwa mkataba unaofanya ukaguzi muhimu wa usalama
2 function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {

Kazi hii pia inapaswa kuitwa kutoka kwa mkataba wa pembeni.

1 require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
2 (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // akiba ya gesi
3 require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
4
5 uint balance0;
6 uint balance1;
7 { // wigo kwa _token{0,1}, huepuka makosa ya rundo kuwa kina sana

Vigezo vya ndani vinaweza kuhifadhiwa ama kwenye kumbukumbu au, ikiwa si vingi sana, moja kwa moja kwenye rundo. Ikiwa tunaweza kupunguza idadi ili tutumie rundo tunatumia gesi kidogo. Kwa maelezo zaidi angalia waraka wa njano, maelezo rasmi ya Ethereum (opens in a new tab), uk. 26, mlinganyo wa 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); // hamisha tokeni kwa matumaini
5 if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // hamisha tokeni kwa matumaini

Uhamisho huu ni wa matumaini, kwa sababu tunahamisha kabla ya kuwa na uhakika masharti yote yametimizwa. Hii ni sawa katika Ethereum kwa sababu ikiwa masharti hayajatimizwa baadaye katika simu tutarejesha kutoka humo na mabadiliko yoyote iliyoyatengeneza.

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

Mjulishe mpokeaji kuhusu ubadilishaji ikiwa imeombwa.

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

Pata salio za sasa. Mkataba wa pembeni hututumia tokeni kabla ya kutuita kwa ubadilishaji. Hii inarahisisha mkataba kuangalia kwamba haufanyiwi udanganyifu, ukaguzi ambao lazima utokee katika mkataba wa msingi (kwa sababu tunaweza kuitwa na vyombo vingine isipokuwa mkataba wetu wa pembeni).

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 { // wigo kwa reserve{0,1}Adjusted, huepuka makosa ya rundo kuwa kina sana
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');

Huu ni ukaguzi wa busara ili kuhakikisha hatupotezi kutokana na ubadilishaji. Hakuna hali yoyote ambayo ubadilishaji unapaswa kupunguza reserve0*reserve1. Hapa pia ndipo tunahakikisha ada ya 0.3% inatumwa kwenye ubadilishaji; kabla ya kuangalia thamani ya K, tunazidisha salio zote mbili kwa 1000 kutoa kiasi kilichozidishwa na 3, hii inamaanisha 0.3% (3/1000 = 0.003 = 0.3%) inakatwa kutoka kwa salio kabla ya kulinganisha thamani yake ya K na thamani ya K ya akiba ya sasa.

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

Sasisha reserve0 na reserve1, na ikihitajika vikusanya bei na mhuri wa muda na toa tukio.

Sawazisha au Punguza

Inawezekana kwa salio halisi kupoteza usawazisho na akiba ambayo ubadilishaji wa jozi unafikiri inayo. Hakuna njia ya kutoa tokeni bila idhini ya mkataba, lakini amana ni jambo tofauti. Akaunti inaweza kuhamisha tokeni kwenye ubadilishanaji bila kuita mint au swap.

Katika hali hiyo kuna suluhisho mbili:

  • sync, sasisha akiba kwa salio za sasa
  • skim, toa kiasi cha ziada. Kumbuka kuwa akaunti yoyote inaruhusiwa kuita skim kwa sababu hatujui ni nani aliyeweka tokeni. Taarifa hii inatolewa katika tukio, lakini matukio hayapatikani kutoka kwenye mnyororo wa bloku.
1 // lazimisha salio kulingana na akiba
2 function skim(address to) external lock {
3 address _token0 = token0; // akiba ya gesi
4 address _token1 = token1; // akiba ya gesi
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 // lazimisha akiba kulingana na salio
12 function sync() external lock {
13 _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
14 }
15}
Onyesha yote

UniswapV2Factory.sol

Mkataba huu (opens in a new tab) unaunda ubadilishanaji wa jozi.

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;

Vigezo hivi vya hali ni muhimu kutekeleza ada ya itifaki (tazama waraka rasmi (opens in a new tab), uk. 5). Anwani ya feeTo inakusanya tokeni za ukwasi kwa ada ya itifaki, na feeToSetter ni anwani inayoruhusiwa kubadilisha feeTo kuwa anwani tofauti.

1 mapping(address => mapping(address => address)) public getPair;
2 address[] public allPairs;

Vigezo hivi vinafuatilia jozi, ubadilishanaji kati ya aina mbili za tokeni.

Ya kwanza, getPair, ni ramani inayotambua mkataba wa ubadilishanaji wa jozi kulingana na tokeni mbili za ERC-20 inazobadilisha. Tokeni za ERC-20 zinatambuliwa na anwani za mikataba inayozitekeleza, kwa hivyo funguo na thamani zote ni anwani. Ili kupata anwani ya ubadilishanaji wa jozi unaokuruhusu kubadilisha kutoka tokenA kwenda tokenB, unatumia getPair[<anwani ya tokenA>][<anwani ya tokenB>] (au kinyume chake).

Kigezo cha pili, allPairs, ni safu inayojumuisha anwani zote za ubadilishanaji wa jozi zilizoundwa na kiwanda hiki. Katika Ethereum huwezi kurudia juu ya yaliyomo kwenye ramani, au kupata orodha ya funguo zote, kwa hivyo kigezo hiki ndiyo njia pekee ya kujua ni ubadilishanaji gani kiwanda hiki kinasimamia.

Kumbuka: Sababu huwezi kurudia juu ya funguo zote za ramani ni kwamba hifadhi ya data ya mkataba ni ghali, kwa hivyo kadiri tunavyotumia kidogo ndivyo bora, na kadiri tunavyoibadilisha mara chache ndivyo bora. Unaweza kuunda ramani zinazounga mkono urudiaji (opens in a new tab), lakini zinahitaji hifadhi ya ziada kwa orodha ya funguo. Katika programu nyingi hauitaji hiyo.

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

Tukio hili hutolewa wakati ubadilishanaji mpya wa jozi unapoundwa. Inajumuisha anwani za tokeni, anwani ya ubadilishanaji wa jozi, na idadi jumla ya ubadilishanaji unaosimamiwa na kiwanda.

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

Kitu pekee ambacho kiunda hufanya ni kubainisha feeToSetter. Viwanda huanza bila ada, na ni feeSetter pekee anayeweza kubadilisha hilo.

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

Kazi hii inarudisha idadi ya jozi za ubadilishanaji.

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

Hii ndiyo kazi kuu ya kiwanda, kuunda ubadilishanaji wa jozi kati ya tokeni mbili za ERC-20. Kumbuka kuwa mtu yeyote anaweza kuita kazi hii. Hauhitaji ruhusa kutoka kwa Uniswap kuunda ubadilishanaji mpya wa jozi.

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

Tunataka anwani ya ubadilishanaji mpya iwe ya uhakika, ili iweze kuhesabiwa mapema nje ya mnyororo (hii inaweza kuwa muhimu kwa mikataba ya Layer 2). Ili kufanya hivi tunahitaji kuwa na mpangilio thabiti wa anwani za tokeni, bila kujali mpangilio tuliopokea, kwa hivyo tunazipanga hapa.

1 require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
2 require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // ukaguzi mmoja unatosha

Mabwawa makubwa ya ukwasi ni bora kuliko madogo, kwa sababu yana bei thabiti zaidi. Hatutaki kuwa na bwawa zaidi ya moja la ukwasi kwa kila jozi ya tokeni. Ikiwa tayari kuna ubadilishanaji, hakuna haja ya kuunda mwingine kwa jozi hiyo hiyo.

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

Ili kuunda mkataba mpya tunahitaji msimbo unaouunda (kazi ya kiunda na msimbo unaoandika kwenye kumbukumbu msimbo wa EVM wa mkataba halisi). Kawaida katika Solidity tunatumia addr = new <jina la mkataba>(<vigezo vya kiunda>) na mkusanyaji hushughulikia kila kitu kwetu, lakini ili kuwa na anwani ya mkataba ya uhakika tunahitaji kutumia opcode ya CREATE2 (opens in a new tab). Wakati msimbo huu ulipoandikwa opcode hiyo bado haikuwa ikiungwa mkono na Solidity, kwa hivyo ilikuwa muhimu kupata msimbo kwa mikono. Hili si tatizo tena, kwa sababu Solidity sasa inasaidia 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 }

Wakati opcode haijaungwa mkono na Solidity bado tunaweza kuiita kwa kutumia mkusanyiko wa ndani (opens in a new tab).

1 IUniswapV2Pair(pair).initialize(token0, token1);

Piga simu kazi ya initialize ili kuuambia ubadilishanaji mpya ni tokeni gani mbili inazobadilisha.

1 getPair[token0][token1] = pair;
2 getPair[token1][token0] = pair; // jaza ramani kwa upande wa nyuma
3 allPairs.push(pair);
4 emit PairCreated(token0, token1, pair, allPairs.length);
5 }

Hifadhi taarifa mpya ya jozi katika vigezo vya hali na toa tukio la kuujulisha ulimwengu kuhusu ubadilishanaji mpya wa jozi.

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}
Onyesha yote

Kazi hizi mbili huruhusu feeSetter kudhibiti mpokeaji wa ada (ikiwepo), na kubadilisha feeSetter kuwa anwani mpya.

UniswapV2ERC20.sol

Mkataba huu (opens in a new tab) unatekeleza tokeni ya ukwasi ya ERC-20. Ni sawa na mkataba wa OpenZeppelin ERC-20, kwa hivyo nitaelezea tu sehemu iliyo tofauti, utendaji wa permit.

Miamala kwenye Ethereum hugharimu ether (ETH), ambayo ni sawa na pesa halisi. Ikiwa una tokeni za ERC-20 lakini huna ETH, huwezi kutuma miamala, kwa hivyo huwezi kufanya chochote nazo. Suluhisho moja la kuepuka tatizo hili ni miamala-meta (opens in a new tab). Mmiliki wa tokeni husaini muamala unaoruhusu mtu mwingine kutoa tokeni nje ya mnyororo na kuutuma kwa kutumia intaneti kwa mpokeaji. Mpokeaji, ambaye ana ETH, kisha huwasilisha kibali kwa niaba ya mmiliki.

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;

Hashi hii ni kitambulisho cha aina ya muamala (opens in a new tab). Ile pekee tunayoiunga mkono hapa ni Permit na vigezo hivi.

1 mapping(address => uint) public nonces;

Haiwezekani kwa mpokeaji kughushi sahihi ya dijitali. Hata hivyo, ni rahisi kutuma muamala huo mara mbili (hii ni aina ya shambulio la kurudia (opens in a new tab)). Ili kuzuia hili, tunatumia nonce (opens in a new tab). Ikiwa nonce ya Permit mpya si moja zaidi ya ile ya mwisho iliyotumika, tunadhania ni batili.

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

Huu ni msimbo wa kupata kitambulisho cha mnyororo (opens in a new tab). Inatumia lahaja ya mkusanyiko wa EVM inayoitwa Yul (opens in a new tab). Kumbuka kuwa katika toleo la sasa la Yul unapaswa kutumia chainid(), si 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 }
Onyesha yote

Hesabu kitenganishi cha kikoa (opens in a new tab) kwa EIP-712.

1 function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {

Hii ndiyo kazi inayotekeleza vibali. Inapokea kama vigezo nyanja husika, na thamani tatu za scalar kwa sahihi (opens in a new tab) (v, r, na s).

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

Usikubali miamala baada ya muda wa mwisho.

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(...) ni ujumbe tunaotarajia kupata. Tunajua nonce inapaswa kuwa nini, kwa hivyo hakuna haja ya kuipata kama kigezo.

Algorithm ya sahihi ya Ethereum inatarajia kupata biti 256 za kusaini, kwa hivyo tunatumia kazi ya hashi ya keccak256.

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

Kutoka kwenye digest na sahihi tunaweza kupata anwani iliyoisaini kwa kutumia ecrecover (opens in a new tab).

1 require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
2 _approve(owner, spender, value);
3 }
4

Ikiwa kila kitu kiko sawa, chukulia hii kama idhinisho la ERC-20 (opens in a new tab).

Mikataba ya Pembeni

Mikataba ya pembeni ni API (kiolesura cha programu) kwa Uniswap. Zinapatikana kwa simu za nje, ama kutoka kwa mikataba mingine au mfumo uliotawanywa. Unaweza kuita mikataba ya msingi moja kwa moja, lakini hiyo ni ngumu zaidi na unaweza kupoteza thamani ukifanya kosa. Mikataba ya msingi ina majaribio tu ya kuhakikisha haifanyiwi udanganyifu, sio ukaguzi wa busara kwa mtu mwingine yeyote. Hayo yako kwenye pembeni ili yaweze kusasishwa kama inavyohitajika.

UniswapV2Router01.sol

Mkataba huu (opens in a new tab) una matatizo, na haupaswi kutumika tena (opens in a new tab). Kwa bahati nzuri, mikataba ya pembeni haina hali na haishikili mali yoyote, kwa hivyo ni rahisi kuiondoa na kupendekeza watu watumie mbadala, UniswapV2Router02, badala yake.

UniswapV2Router02.sol

Katika hali nyingi utatumia Uniswap kupitia mkataba huu (opens in a new tab). Unaweza kuona jinsi ya kuitumia hapa (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';
Onyesha yote

Mengi ya haya tumeyakuta hapo awali, au ni dhahiri kabisa. Ubaguzi mmoja ni IWETH.sol. Uniswap v2 inaruhusu ubadilishanaji kwa jozi yoyote ya tokeni za ERC-20, lakini ether (ETH) yenyewe si tokeni ya ERC-20. Ilikuwepo kabla ya kiwango hicho na inahamishwa kwa mifumo ya kipekee. Ili kuwezesha matumizi ya ETH katika mikataba inayotumika kwa tokeni za ERC-20 watu walikuja na mkataba wa ether iliyofungwa (WETH) (opens in a new tab). Unatuma ETH kwa mkataba huu, na inakuzalishia kiasi sawa cha WETH. Au unaweza kuondoa WETH, na kupata ETH tena.

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

Router inahitaji kujua ni kiwanda gani cha kutumia, na kwa miamala inayohitaji WETH ni mkataba gani wa WETH wa kutumia. Thamani hizi hazibadiliki (opens in a new tab), ikimaanisha zinaweza kuwekwa tu katika kiunda. Hii inawapa watumiaji ujasiri kwamba hakuna mtu atakayeweza kuzibadilisha ili zielekeze kwenye mikataba isiyo ya uaminifu.

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

Kirekebishaji hiki huhakikisha kuwa miamala yenye kikomo cha muda ("fanya X kabla ya muda Y ukiweza") haifanyiki baada ya kikomo chao cha muda.

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

Kiunda huweka tu vigezo vya hali visivyobadilika.

1 receive() external payable {
2 assert(msg.sender == WETH); // kubali ETH tu kupitia fallback kutoka kwa mkataba wa WETH
3 }

Kazi hii inaitwa tunapokomboa tokeni kutoka kwa mkataba wa WETH kurudi kuwa ETH. Mkataba wa WETH pekee tunaotumia ndio umeruhusiwa kufanya hivyo.

Ongeza Ukwasi

Kazi hizi huongeza tokeni kwenye ubadilishanaji wa jozi, ambayo huongeza bwawa la ukwasi.

1
2 // **** ONGEZA UKWASI ****
3 function _addLiquidity(

Kazi hii inatumika kuhesabu kiasi cha tokeni A na B ambazo zinapaswa kuwekwa kwenye ubadilishanaji wa jozi.

1 address tokenA,
2 address tokenB,

Hizi ni anwani za mikataba ya tokeni za ERC-20.

1 uint amountADesired,
2 uint amountBDesired,

Hivi ni viwango ambavyo mtoa huduma wa ukwasi anataka kuweka. Pia ni viwango vya juu vya A na B vitakavyowekwa.

1 uint amountAMin,
2 uint amountBMin

Hivi ni viwango vya chini vinavyokubalika vya kuweka. Ikiwa muamala hauwezi kufanyika kwa viwango hivi au zaidi, rejesha kutoka humo. Ikiwa hutaki kipengele hiki, weka sifuri.

Watoa huduma za ukwasi huweka kiwango cha chini, kwa kawaida, kwa sababu wanataka kupunguza muamala kwa kiwango cha ubadilishanaji kilicho karibu na cha sasa. Ikiwa kiwango cha ubadilishanaji kinabadilika sana inaweza kumaanisha habari zinazobadilisha thamani za msingi, na wanataka kuamua wenyewe nini cha kufanya.

Kwa mfano, fikiria kesi ambapo kiwango cha ubadilishanaji ni moja kwa moja na mtoa huduma wa ukwasi anaweka thamani hizi:

KigezoThamani
amountADesired1000
amountBDesired1000
amountAMin900
amountBMin800

Muda wote kiwango cha ubadilishanaji kinapobaki kati ya 0.9 na 1.25, muamala hufanyika. Ikiwa kiwango cha ubadilishanaji kinatoka nje ya masafa hayo, muamala hughairishwa.

Sababu ya tahadhari hii ni kwamba miamala si ya papo hapo, unaiwasilisha na hatimaye mthibitishaji ataijumuisha kwenye kizuizi (isipokuwa bei yako ya gesi ni ya chini sana, katika hali hiyo utahitaji kuwasilisha muamala mwingine na nonce sawa na bei ya juu ya gesi ili kuibatilisha). Huwezi kudhibiti kinachotokea wakati wa muda kati ya uwasilishaji na ujumuishaji.

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

Kazi inarudisha viwango ambavyo mtoa huduma wa ukwasi anapaswa kuweka ili kuwa na uwiano sawa na uwiano wa sasa kati ya akiba.

1 // unda jozi ikiwa bado haipo
2 if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
3 IUniswapV2Factory(factory).createPair(tokenA, tokenB);
4 }

Ikiwa hakuna ubadilishanaji wa jozi hii ya tokeni bado, uunde.

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

Pata akiba za sasa katika jozi.

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

Ikiwa akiba za sasa ni tupu basi huu ni ubadilishanaji mpya wa jozi. Viwango vitakavyowekwa vinapaswa kuwa sawa kabisa na vile ambavyo mtoa huduma wa ukwasi anataka kutoa.

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

Ikiwa tunahitaji kuona viwango vitakavyokuwa, tunapata kiwango bora kwa kutumia kazi hii (opens in a new tab). Tunataka uwiano sawa na akiba za sasa.

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

Ikiwa amountBOptimal ni ndogo kuliko kiwango ambacho mtoa huduma wa ukwasi anataka kuweka inamaanisha tokeni B ina thamani zaidi kwa sasa kuliko mweka amana wa ukwasi anavyofikiri, kwa hivyo kiwango kidogo kinahitajika.

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

Ikiwa kiwango bora cha B ni zaidi ya kiwango kinachotakiwa cha B inamaanisha tokeni za B zina thamani ndogo kwa sasa kuliko mweka amana wa ukwasi anavyofikiri, kwa hivyo kiwango cha juu kinahitajika. Hata hivyo, kiwango kinachotakiwa ni cha juu, kwa hivyo hatuwezi kufanya hivyo. Badala yake tunahesabu idadi bora ya tokeni A kwa kiwango kinachotakiwa cha tokeni B.

Tukiweka yote pamoja tunapata grafu hii. Fikiria unajaribu kuweka tokeni elfu moja za A (mstari wa bluu) na tokeni elfu moja za B (mstari mwekundu). Mhimili wa x ni kiwango cha ubadilishanaji, A/B. Ikiwa x=1, zina thamani sawa na unaweka elfu moja ya kila moja. Ikiwa x=2, A ina thamani mara mbili ya B (unapata tokeni mbili za B kwa kila tokeni A) kwa hivyo unaweka tokeni elfu moja za B, lakini tokeni 500 tu za A. Ikiwa x=0.5, hali inabadilika, tokeni elfu moja za A na tokeni mia tano za B.

Grafu

Unaweza kuweka ukwasi moja kwa moja kwenye mkataba wa msingi (kwa kutumia UniswapV2Pair::mint (opens in a new tab)), lakini mkataba wa msingi huangalia tu kwamba haufanyiwi udanganyifu, kwa hivyo una hatari ya kupoteza thamani ikiwa kiwango cha ubadilishanaji kinabadilika kati ya wakati unapowasilisha muamala wako na wakati unapotekelezwa. Ikiwa unatumia mkataba wa pembeni, huhesabu kiwango unachopaswa kuweka na kukiweka mara moja, kwa hivyo kiwango cha ubadilishanaji hakibadiliki na hupotezi chochote.

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
Onyesha yote

Kazi hii inaweza kuitwa na muamala ili kuweka ukwasi. Vigezo vingi ni sawa na katika _addLiquidity hapo juu, isipokuwa mbili:

. to ni anwani inayopata tokeni mpya za ukwasi zilizozalishwa kuonyesha sehemu ya mtoa huduma wa ukwasi ya bwawa . deadline ni kikomo cha muda kwenye muamala

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

Tunahesabu kiasi cha kuweka halisi na kisha kupata anwani ya bwawa la ukwasi. Ili kuokoa gesi hatufanyi hivi kwa kuuliza kiwanda, lakini kwa kutumia kazi ya maktaba pairFor (tazama hapa chini katika maktaba)

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

Hamisha kiasi sahihi cha tokeni kutoka kwa mtumiaji hadi kwenye ubadilishanaji wa jozi.

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

Kwa malipo, mpe anwani ya to tokeni za ukwasi kwa umiliki wa sehemu ya bwawa. Kazi ya mint ya mkataba wa msingi huona ni tokeni ngapi za ziada inazo (ikilinganishwa na ilivyokuwa mara ya mwisho ukwasi ulipobadilika) na inazalisha ukwasi ipasavyo.

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

Wakati mtoa huduma wa ukwasi anapotaka kutoa ukwasi kwa ubadilishanaji wa jozi ya Token/ETH, kuna tofauti chache. Mkataba hushughulikia kufunga ETH kwa ajili ya mtoa huduma wa ukwasi. Hakuna haja ya kubainisha ni ETH ngapi mtumiaji anataka kuweka, kwa sababu mtumiaji huwatuma tu na muamala (kiasi kinapatikana katika 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));
Onyesha yote

Ili kuweka ETH, mkataba kwanza huifunga kuwa WETH na kisha huhamisha WETH kwenye jozi. Ona kuwa uhamisho umefungwa kwenye assert. Hii inamaanisha kuwa ikiwa uhamisho utashindwa, simu hii ya mkataba pia itashindwa, na kwa hivyo ufungaji hautatokea.

1 liquidity = IUniswapV2Pair(pair).mint(to);
2 // rudisha vumbi la eth, ikiwapo
3 if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
4 }

Mtumiaji tayari ametutumia ETH, kwa hivyo ikiwa kuna ziada yoyote iliyobaki (kwa sababu tokeni nyingine ina thamani ndogo kuliko mtumiaji alivyofikiria), tunahitaji kurejesha pesa.

Ondoa Ukwasi

Kazi hizi zitaondoa ukwasi na kumlipa mtoa huduma wa ukwasi.

1 // **** ONDOA UKWASI ****
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) {
Onyesha yote

Kesi rahisi zaidi ya kuondoa ukwasi. Kuna kiasi cha chini cha kila tokeni ambacho mtoa huduma wa ukwasi anakubali kupokea, na lazima itokee kabla ya muda wa mwisho.

1 address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
2 IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // tuma ukwasi kwenye jozi
3 (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);

Kazi ya burn ya mkataba wa msingi inashughulikia kumlipa mtumiaji tokeni.

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

Wakati kazi inaporudisha thamani nyingi, lakini tunavutiwa na baadhi tu, hivi ndivyo tunavyopata thamani hizo tu. Ni nafuu kidogo kwa gharama ya gesi kuliko kusoma thamani na kutoitumia.

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

Tafsiri viwango kutoka jinsi mkataba wa msingi unavyovirudisha (tokeni ya anwani ya chini kwanza) hadi jinsi mtumiaji anavyotarajia (sawa na tokenA na tokenB).

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

Ni sawa kufanya uhamisho kwanza na kisha kuthibitisha ni halali, kwa sababu ikiwa si hivyo tutarejesha kutoka kwa mabadiliko yote ya hali.

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 }
Onyesha yote

Kuondoa ukwasi kwa ETH ni karibu sawa, isipokuwa kwamba tunapokea tokeni za WETH na kisha kuzikomboa kwa ETH ili kumrudishia mtoa huduma wa ukwasi.

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 }
Onyesha yote

Kazi hizi huwasilisha miamala-meta ili kuruhusu watumiaji wasio na ether kutoa kutoka kwenye bwawa, kwa kutumia mfumo wa kibali.

1
2 // **** ONDOA UKWASI (kuunga mkono tokeni za ada-kwa-uhamisho) ****
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
Onyesha yote

Kazi hii inaweza kutumika kwa tokeni ambazo zina ada za uhamisho au hifadhi. Wakati tokeni ina ada kama hizo hatuwezi kutegemea kazi ya removeLiquidity kutuambia ni kiasi gani cha tokeni tunachopata, kwa hivyo tunahitaji kutoa kwanza na kisha kupata salio.

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 }
Onyesha yote

Shughuli ya mwisho inaunganisha ada za ghala na shughuli za meta.

Fanya biashara

1 // **** BADILISHA ****
2 // inahitaji kiasi cha awali kuwa tayari kimetumwa kwa jozi ya kwanza
3 function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {

Shughuli hii hufanya uchakataji wa ndani ambao unahitajika kwa shughuli zinazoonyeshwa kwa wafanyabiashara.

1 for (uint i; i < path.length - 1; i++) {

Ninapoandika hivi, kuna tokeni 388,160 za ERC-20 (opens in a new tab). Ikiwa kungekuwa na ubadilishanaji wa jozi kwa kila jozi ya tokeni, kungekuwa na zaidi ya ubadilishanaji wa jozi bilioni 150. Mnyororo mzima, kwa sasa, una akaunti 0.1% tu ya idadi hiyo (opens in a new tab). Badala yake, shughuli za ubadilishaji zinasaidia dhana ya njia. Mfanyabiashara anaweza kubadilisha A kwa B, B kwa C, na C kwa D, kwa hivyo hakuna haja ya ubadilishanaji wa moja kwa moja wa jozi ya A-D.

Bei kwenye masoko haya huwa zinasawazishwa, kwa sababu zinapokuwa hazijasawazishwa, inaunda fursa ya upatanishi. Fikiria, kwa mfano, tokeni tatu, A, B, na C. Kuna ubadilishanaji wa jozi tatu, moja kwa kila jozi.

  1. Hali ya awali
  2. Mfanyabiashara anauza tokeni 24.695 za A na anapata tokeni 25.305 za B.
  3. Mfanyabiashara anauza tokeni 24.695 za B kwa tokeni 25.305 za C, akihifadhi takriban tokeni 0.61 za B kama faida.
  4. Kisha mfanyabiashara anauza tokeni 24.695 za C kwa tokeni 25.305 za A, akihifadhi takriban tokeni 0.61 za C kama faida. Mfanyabiashara pia ana tokeni za ziada 0.61 za A (tokeni 25.305 anazopata mwishoni, ukitoa uwekezaji wa awali wa 24.695).
HatuaUbadilishanaji wa A-BUbadilishanaji wa B-CUbadilishanaji wa 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];

Pata jozi tunayoishughulikia sasa, ipange (kwa matumizi na jozi hiyo) na upate kiasi kinachotarajiwa cha matokeo.

1 (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));

Pata kiasi kinachotarajiwa, kikiwa kimepangwa jinsi ubadilishanaji wa jozi unavyotarajia.

1 address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;

Je, huu ndio ubadilishanaji wa mwisho? Ikiwa ndivyo, tuma tokeni zilizopokelewa kwa ajili ya biashara hiyo kwenye eneo linalolengwa. Ikiwa sivyo, itume kwenye ubadilishanaji wa jozi unaofuata.

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

Ita shughuli ya ubadilishanaji wa jozi ili kubadilisha tokeni. Hatuhitaji kurudishiwa taarifa kuhusu ubadilishanaji, kwa hivyo hatutumi baiti zozote katika sehemu hiyo.

1 function swapExactTokensForTokens(

Shughuli hii hutumiwa moja kwa moja na wafanyabiashara kubadilisha tokeni moja kwa nyingine.

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

Kigezo hiki kina anwani za mikataba ya ERC-20. Kama ilivyoelezwa hapo juu, hii ni safu kwa sababu unaweza kuhitaji kupitia ubadilishanaji kadhaa wa jozi ili kutoka kwenye rasilimali uliyonayo hadi rasilimali unayoitaka.

Kigezo cha shughuli katika Solidity kinaweza kuhifadhiwa aidha katika memory au calldata. Ikiwa shughuli ni sehemu ya kuingilia kwenye mkataba, inayoitwa moja kwa moja kutoka kwa mtumiaji (kwa kutumia muamala) au kutoka kwa mkataba tofauti, basi thamani ya kigezo inaweza kuchukuliwa moja kwa moja kutoka kwa data ya wito. Ikiwa shughuli inaitwa ndani, kama _swap hapo juu, basi vigezo vinapaswa kuhifadhiwa katika memory. Kwa mtazamo wa mkataba unaoitwa, calldata ni ya kusoma tu.

Kwa aina za scalar kama vile uint au address mkusanyaji hutushughulikia uchaguzi wa ghala, lakini kwa safu, ambazo ni ndefu na za gharama kubwa zaidi, tunabainisha aina ya ghala itakayotumika.

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

Thamani za kurudisha daima hurudishwa katika memory.

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

Hesabu kiasi kitakachonunuliwa katika kila ubadilishaji. Ikiwa matokeo ni chini ya kiwango cha chini ambacho mfanyabiashara yuko tayari kukubali, rudisha nyuma muamala.

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

Hatimaye, hamisha tokeni ya awali ya ERC-20 kwenye akaunti kwa ajili ya ubadilishanaji wa jozi ya kwanza na uite _swap. Haya yote yanatokea katika muamala mmoja, kwa hivyo ubadilishanaji wa jozi unajua kuwa tokeni zozote zisizotarajiwa ni sehemu ya uhamisho huu.

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 }
Onyesha yote

Shughuli iliyotangulia, swapTokensForTokens, inaruhusu mfanyabiashara kubainisha idadi kamili ya tokeni za ingizo anazotaka kutoa na idadi ya chini zaidi ya tokeni za pato anazotaka kupokea. Shughuli hii hufanya ubadilishaji kinyume, inamruhusu mfanyabiashara kubainisha idadi ya tokeni za pato anazotaka, na idadi ya juu zaidi ya tokeni za ingizo anazotaka kulipia.

Katika visa vyote viwili, mfanyabiashara anapaswa kwanza kuupa mkataba huu wa pembeni ruhusa ili kuuruhusu kuhamisha.

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 // refund dust eth, if any
72 if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
73 }
Onyesha yote

Aina hizi nne zote zinahusisha biashara kati ya ETH na tokeni. Tofauti pekee ni kwamba tunapokea ETH kutoka kwa mfanyabiashara na kuitumia kuunda WETH, au tunapokea WETH kutoka kwa ubadilishanaji wa mwisho katika njia na kuiteketeza, na kumrudishia mfanyabiashara ETH inayotokana.

1 // **** BADILISHA (inasaidia tokeni za ada-kwa-uhamisho) ****
2 // inahitaji kiasi cha awali kuwa tayari kimetumwa kwa jozi ya kwanza
3 function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {

Hii ni shughuli ya ndani ya kubadilisha tokeni ambazo zina ada za uhamisho au ghala ili kutatua (suala hili (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 { // wigo wa kuepuka makosa ya "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);
Onyesha yote

Kwa sababu ya ada za uhamisho, hatuwezi kutegemea shughuli ya getAmountsOut kutuambia ni kiasi gani tunapata kutoka kwa kila uhamisho (kama tunavyofanya kabla ya kuita _swap ya awali). Badala yake, inabidi tuhamishe kwanza kisha tuone ni tokeni ngapi tulipata.

Kumbuka: Kinadharia tungeweza tu kutumia shughuli hii badala ya _swap, lakini katika hali fulani (kwa mfano, ikiwa uhamisho utaishia kurejeshwa kwa sababu hakuna kiasi cha kutosha mwishoni kufikia kiwango cha chini kinachohitajika) hiyo ingegharimu gesi zaidi. Tokeni za ada za uhamisho ni nadra sana, kwa hivyo ingawa tunahitaji kuzijumuisha, hakuna haja ya ubadilishaji wote kudhani kuwa zinapitia angalau mojawapo.

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 }
Onyesha yote

Hizi ni aina zilezile zinazotumika kwa tokeni za kawaida, lakini zinaita _swapSupportingFeeOnTransferTokens badala yake.

1 // **** SHUGHULI ZA MAKTABA ****
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}
Onyesha yote

Shughuli hizi ni proksi tu zinazoita shughuli za UniswapV2Library.

UniswapV2Migrator.sol

Mkataba huu ulitumika kuhamisha ubadilishanaji kutoka v1 ya zamani hadi v2. Sasa kwa kuwa zimehamishwa, haihusiki tena.

Maktaba

Maktaba ya SafeMath (opens in a new tab) imeandikwa vizuri, kwa hivyo hakuna haja ya kuiandika hapa.

Hisabati

Maktaba hii ina baadhi ya shughuli za hisabati ambazo kwa kawaida hazihitajiki katika msimbo wa Solidity, kwa hivyo si sehemu ya lugha.

1pragma solidity =0.5.16;
2
3// maktaba ya kufanya shughuli mbalimbali za hisabati
4
5library Math {
6 function min(uint x, uint y) internal pure returns (uint z) {
7 z = x < y ? x : y;
8 }
9
10 // njia ya kibabiloni (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;
Onyesha yote

Anza na x kama makadirio ambayo ni ya juu kuliko kipeuo cha pili (ndiyo sababu tunahitaji kushughulikia 1-3 kama kesi maalum).

1 while (x < z) {
2 z = x;
3 x = (y / x + x) / 2;

Pata makadirio ya karibu zaidi, wastani wa makadirio ya awali na nambari ambayo tunajaribu kupata kipeuo chake cha pili ukigawanya kwa makadirio ya awali. Rudia hadi makadirio mapya yasiwe chini ya yale yaliyopo. Kwa maelezo zaidi, angalia hapa (opens in a new tab).

1 }
2 } else if (y != 0) {
3 z = 1;

Hatupaswi kamwe kuhitaji kipeuo cha pili cha sifuri. Vipeuo vya pili vya moja, mbili, na tatu ni takriban moja (tunatumia nambari kamili, kwa hivyo tunapuuza sehemu).

1 }
2 }
3}

Sehemu za Nukta Zisizobadilika (UQ112x112)

Maktaba hii hushughulikia sehemu, ambazo kwa kawaida si sehemu ya hesabu za Ethereum. Hufanya hivi kwa kusimba nambari x kama x*2^112. Hii inaturuhusu kutumia opcodes za awali za kujumlisha na kutoa bila mabadiliko.

1pragma solidity =0.5.16;
2
3// maktaba ya kushughulikia nambari za nukta zisizobadilika za binary (https://wikipedia.org/wiki/Q_(number_format))
4
5// masafa: [0, 2**112 - 1]
6// azimio: 1 / 2**112
7
8library UQ112x112 {
9 uint224 constant Q112 = 2**112;
Onyesha yote

Q112 ni usimbaji kwa moja.

1 // simba uint112 kama UQ112x112
2 function encode(uint112 y) internal pure returns (uint224 z) {
3 z = uint224(y) * Q112; // haifuriki kamwe
4 }

Kwa sababu y ni uint112, thamani yake kubwa zaidi inaweza kuwa 2^112-1. Nambari hiyo bado inaweza kusimbwa kama UQ112x112.

1 // gawanya UQ112x112 kwa uint112, kurudisha UQ112x112
2 function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
3 z = x / uint224(y);
4 }
5}

Tukigawanya thamani mbili za UQ112x112, matokeo hayazidishwi tena na 2^112. Kwa hivyo badala yake tunachukua nambari kamili kwa ajili ya denomineta. Tungehitaji kutumia mbinu kama hiyo kufanya kuzidisha, lakini hatuhitaji kufanya kuzidisha kwa thamani za UQ112x112.

UniswapV2Library

Maktaba hii inatumiwa tu na mikataba ya pembeni

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 // inarudisha anwani za tokeni zilizopangwa, zinazotumika kushughulikia thamani za kurudisha kutoka kwa jozi zilizopangwa kwa utaratibu huu
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 }
Onyesha yote

Panga tokeni mbili kwa anwani, ili tuweze kupata anwani ya ubadilishanaji wa jozi kwa ajili yao. Hii ni muhimu kwa sababu vinginevyo tungekuwa na uwezekano mbili, moja kwa vigezo A,B na nyingine kwa vigezo B,A, na kusababisha ubadilishanaji mbili badala ya moja.

1 // huhesabu anwani ya CREATE2 kwa jozi bila kufanya wito wowote wa nje
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' // hashi ya msimbo wa kuanzisha
9 ))));
10 }
Onyesha yote

Shughuli hii huhesabu anwani ya ubadilishanaji wa jozi kwa tokeni hizo mbili. Mkataba huu umeundwa kwa kutumia opcode ya CREATE2 (opens in a new tab), kwa hivyo tunaweza kuhesabu anwani kwa kutumia algoriti ileile ikiwa tunajua vigezo inavyotumia. Hii ni nafuu sana kuliko kuuliza kiwanda, na

1 // huchukua na kupanga hifadhi za jozi
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 }

Shughuli hii inarudisha hifadhi za tokeni mbili ambazo ubadilishanaji wa jozi unazo. Kumbuka kuwa inaweza kupokea tokeni kwa mpangilio wowote, na kuzipanga kwa matumizi ya ndani.

1 // kwa kuzingatia kiasi fulani cha rasilimali na hifadhi za jozi, inarudisha kiasi sawa cha rasilimali nyingine
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 }

Shughuli hii inakupa kiasi cha tokeni B utakachopata kwa kubadilishana na tokeni A ikiwa hakuna ada inayohusika. Hesabu hii inazingatia kwamba uhamisho hubadilisha kiwango cha ubadilishaji.

1 // kwa kuzingatia kiasi cha ingizo cha rasilimali na hifadhi za jozi, inarudisha kiasi cha juu cha pato la rasilimali nyingine
2 function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {

Shughuli ya quote hapo juu inafanya kazi vizuri ikiwa hakuna ada ya kutumia ubadilishanaji wa jozi. Hata hivyo, ikiwa kuna ada ya ubadilishaji ya 0.3%, kiasi unachopata hasa ni kidogo. Shughuli hii huhesabu kiasi baada ya ada ya ubadilishaji.

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 haishughulikii sehemu kwa asili, kwa hivyo hatuwezi tu kuzidisha kiasi cha pato kwa 0.997. Badala yake, tunazidisha nambari ya juu (numerator) kwa 997 na nambari ya chini (denominator) kwa 1000, na kupata athari sawa.

1 // kwa kuzingatia kiasi cha pato cha rasilimali na hifadhi za jozi, inarudisha kiasi kinachohitajika cha ingizo la rasilimali nyingine
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 }

Shughuli hii hufanya takribani kitu kilekile, lakini inapata kiasi cha pato na kutoa ingizo.

1
2 // hufanya hesabu za mnyororo za getAmountOut kwenye idadi yoyote ya jozi
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 // hufanya hesabu za mnyororo za getAmountIn kwenye idadi yoyote ya jozi
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}
Onyesha yote

Shughuli hizi mbili hushughulikia utambuzi wa thamani wakati inahitajika kupitia ubadilishanaji kadhaa wa jozi.

Msaidizi wa Uhamisho

Maktaba hii (opens in a new tab) huongeza ukaguzi wa mafanikio karibu na uhamisho wa ERC-20 na Ethereum ili kushughulikia urejeshaji na urejeshaji wa thamani ya false kwa njia ileile.

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3pragma solidity >=0.6.0;
4
5// njia za msaada za kuingiliana na tokeni za ERC20 na kutuma ETH ambazo hazirudishi true/false kwa uthabiti
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
Onyesha yote

Tunaweza kuita mkataba tofauti kwa mojawapo ya njia mbili:

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

Kwa ajili ya utangamano wa nyuma na tokeni zilizoundwa kabla ya kiwango cha ERC-20, wito wa ERC-20 unaweza kushindwa ama kwa kurejesha (ambapo success ni false) au kwa kufanikiwa na kurudisha thamani ya false (ambapo kuna data ya pato, na ukii-decode kama boolean unapata 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 }
Onyesha yote

Shughuli hii inatekeleza utendaji wa uhamisho wa ERC-20 (opens in a new tab), ambayo inaruhusu akaunti kutumia ruhusa iliyotolewa na akaunti tofauti.

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 }
Onyesha yote

Shughuli hii inatekeleza utendaji wa transferFrom wa ERC-20 (opens in a new tab), ambayo inaruhusu akaunti kutumia ruhusa iliyotolewa na akaunti tofauti.

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}

Shughuli hii huhamisha ether kwenye akaunti. Wito wowote kwa mkataba tofauti unaweza kujaribu kutuma ether. Kwa sababu hatuhitaji hasa kuita shughuli yoyote, hatutumi data yoyote na wito huo.

Hitimisho

Hii ni makala ndefu ya takriban kurasa 50. Ikiwa umefika hapa, hongera! Tunatumai sasa umeelewa mazingatio katika kuandika programu halisi ya maisha (kinyume na programu fupi za sampuli) na uko tayari zaidi kuweza kuandika mikataba kwa ajili ya matumizi yako mwenyewe.

Sasa nenda ukaandike kitu muhimu na utushangaze.

Tazama hapa kwa kazi zangu zaidi (opens in a new tab).

Ukurasa ulihaririwa mwisho: 25 Februari 2026

Umesaidika na mafunzo haya?