Ruka hadi kwenye maudhui makuu

Mwongozo wa Mkataba wa Uniswap-v2

Solidity
dapps
Kati
Ori Pomerantz
1 Mei 2021
57 dakika za kusoma

Utangulizi

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

Uniswap Inafanya Nini?

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

Watoa ukwasi hupatia bwawa tokeni mbili zinazoweza 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, kutuma Token0 na kupokea Token1) kutoka kwenye bwawa lililotolewa na watoa ukwasi. Kiwango cha ubadilishaji kinatambuliwa na idadi ya uwiano ya Token0 na Token1 ambayo bwawa linayo. Kwa kuongezea, bwawa huchukua asilimia ndogo kama tuzo kwa bwawa la ukwasi.

Wakati watoa ukwasi wanapotaka rasilimali zao zirudishwe wanaweza kuteketeza tokeni za bwawa na kupokea tena 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 toleo lililoboreshwa ambalo ni gumu zaidi kuliko v2. Ni rahisi zaidi kujifunza v2 kwanza na kisha kwenda kwenye v3.

Mikataba ya Msingi dhidi ya Mikataba ya Pembezoni

Uniswap v2 imegawanywa katika vipengele viwili, msingi na pembezoni. Mgawanyiko huu unaruhusu mikataba ya msingi, ambayo inashikilia rasilimali na kwa hivyo lazima iwe salama, kuwa rahisi na nyepesi kukagua. Utendaji wote wa ziada unaohitajika na wafanyabiashara unaweza kutolewa na mikataba ya pembezoni.

Mtiririko wa Data na Udhibiti

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

  1. Badilishano kati ya tokeni tofauti
  2. Ongeza ukwasi kwenye soko na upate tuzo ya tokeni za ukwasi za ERC-20 za ubadilishanaji wa jozi
  3. Teketeza tokeni za ukwasi za ERC-20 na upate tena tokeni za ERC-20 ambazo ubadilishanaji wa jozi unaruhusu wafanyabiashara kubadilishana

Badilishano

Huu ndio mtiririko wa kawaida zaidi, unaotumiwa na wafanyabiashara:

Mwitaji

  1. Ipe akaunti ya pembezoni kibali cha kiasi kinachopaswa kubadilishwa.
  2. Ita mojawapo ya kazi nyingi za badilishano za mkataba wa pembezoni (ipi inategemea kama ETH inahusika au la, kama mfanyabiashara anabainisha kiasi cha tokeni za kuweka au kiasi cha tokeni za kupata tena, n.k). Kila kazi ya badilishano inakubali path, safu ya mabadilishano ya kupitia.

Katika mkataba wa pembezoni (UniswapV2Router02.sol)

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

Katika mkataba wa msingi (UniswapV2Pair.sol)

  1. Thibitisha kuwa mkataba wa msingi haudanganywi na unaweza kudumisha ukwasi wa kutosha baada ya badilishano.
  2. Angalia tuna tokeni ngapi za ziada pamoja na akiba inayojulikana. Kiasi hicho ni idadi ya tokeni za kuingiza tulizopokea ili kubadilishana.
  3. Tuma tokeni za kutoa kwenye kituo cha mwisho.
  4. Ita _update ili kusasisha kiasi cha akiba

Kurudi kwenye mkataba wa pembezoni (UniswapV2Router02.sol)

  1. Fanya usafishaji wowote unaohitajika (kwa mfano, teketeza tokeni za WETH ili kupata tena ETH ya kumtumia mfanyabiashara)

Ongeza Ukwasi

Mwitaji

  1. Ipe akaunti ya pembezoni kibali cha kiasi kinachopaswa kuongezwa kwenye bwawa la ukwasi.
  2. Ita mojawapo ya kazi za addLiquidity za mkataba wa pembezoni.

Katika mkataba wa pembezoni (UniswapV2Router02.sol)

  1. Unda ubadilishanaji mpya wa jozi ikiwa ni lazima
  2. Ikiwa kuna ubadilishanaji wa jozi uliopo, hesabu kiasi cha tokeni za kuongeza. Hii inapaswa kuwa thamani sawa kwa tokeni zote mbili, kwa hivyo uwiano sawa wa tokeni mpya kwa tokeni zilizopo.
  3. Angalia ikiwa kiasi kinakubalika (waitaji wanaweza kubainisha kiasi cha chini ambacho chini yake wangependelea kutoongeza ukwasi)
  4. Ita mkataba wa msingi.

Katika mkataba wa msingi (UniswapV2Pair.sol)

  1. Fua tokeni za ukwasi na uzitume kwa mwitaji
  2. Ita _update ili kusasisha kiasi cha akiba

Ondoa Ukwasi

Mwitaji

  1. Ipe akaunti ya pembezoni kibali cha tokeni za ukwasi za kuteketezwa kwa kubadilishana na tokeni za msingi.
  2. Ita mojawapo ya kazi za removeLiquidity za mkataba wa pembezoni.

Katika mkataba wa pembezoni (UniswapV2Router02.sol)

  1. Tuma tokeni za ukwasi kwenye ubadilishanaji wa jozi

Katika mkataba wa msingi (UniswapV2Pair.sol)

  1. Tuma anwani ya mwisho tokeni za msingi kwa uwiano wa tokeni zilizoteketezwa. Kwa mfano ikiwa kuna tokeni 1000 za A kwenye bwawa, tokeni 500 za B, na tokeni za ukwasi 90, na tunapokea tokeni 9 za kuteketeza, tunateketeza 10% ya tokeni za ukwasi kwa hivyo tunamrudishia mtumiaji tokeni 100 za A na tokeni 50 za B.
  2. Teketeza tokeni za ukwasi
  3. Ita _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 ambalo hubadilishana tokeni. Ni utendaji wa msingi wa Uniswap.

Hizi ni miingiliano yote ambayo mkataba unahitaji kujua, iwe kwa sababu mkataba unazitekeleza (IUniswapV2Pair na UniswapV2ERC20) au kwa sababu unaita mikataba inayoitekeleza.

contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {

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

    using SafeMath  for uint;

Maktaba ya SafeMath (opens in a new tab) inatumika kuepuka kufurika na kupungua. Hili ni muhimu kwa sababu vinginevyo tunaweza kuishia na hali ambapo thamani inapaswa kuwa -1, lakini badala yake inakuwa 2^256-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, zikiwa 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 katika waraka huu.

Vigezo

    uint public constant MINIMUM_LIQUIDITY = 10**3;

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

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

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

    address public factory;

Huu ni mkataba wa kiwanda uliounda bwawa hili. Kila bwawa ni badilishano kati ya tokeni mbili za ERC-20, kiwanda ni kituo kikuu kinachounganisha mabwawa haya yote.

    address public token0;
    address public token1;

Kuna anwani za mikataba kwa aina mbili za tokeni za ERC-20 ambazo zinaweza kubadilishwa na bwawa hili.

    uint112 private reserve0;           // inatumia nafasi moja ya hifadhi, inafikika kupitia getReserves
    uint112 private reserve1;           // inatumia nafasi moja ya hifadhi, inafikika kupitia getReserves

Akiba ambazo bwawa linazo kwa kila aina ya tokeni. Tunachukulia kwamba zote mbili zinawakilisha kiasi sawa cha thamani, na kwa hivyo kila token0 ina thamani ya reserve1/reserve0 ya token1.

    uint32  private blockTimestampLast; // inatumia nafasi moja ya hifadhi, inafikika kupitia getReserves

Mhuri wa muda kwa kitalu cha mwisho ambacho badilishano lilitokea, kinachotumika kufuatilia viwango vya ubadilishaji kwa muda.

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

    uint public price0CumulativeLast;
    uint public price1CumulativeLast;

Vigezo hivi vinashikilia gharama limbikizi kwa kila tokeni (kila moja kwa kulinganisha na nyingine). Zinaweza kutumika kukokotoa wastani wa kiwango cha ubadilishaji kwa kipindi cha muda.

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

Njia ambayo badilishano la jozi huamua kiwango cha ubadilishaji kati ya token0 na token1 ni kuweka zao la akiba mbili kuwa thabiti wakati wa biashara. kLast ni thamani hii. Inabadilika wakati mtoa ukwasi anaweka au kutoa tokeni, na inaongezeka kidogo kwa sababu ya ada ya soko ya 0.3%.

Hapa kuna mfano rahisi. Kumbuka kwamba kwa 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 * reserve1Wastani wa kiwango cha ubadilishaji (token1 / token0)
Usanidi wa awali1,000.0001,000.0001,000,000
Mfanyabiashara A anabadilisha token0 50 kwa token1 47.6191,050.000952.3811,000,0000.952
Mfanyabiashara B anabadilisha token0 10 kwa token1 8.9841,060.000943.3961,000,0000.898
Mfanyabiashara C anabadilisha token0 40 kwa token1 34.3051,100.000909.0901,000,0000.858
Mfanyabiashara D anabadilisha token1 100 kwa token0 109.01990.9901,009.0901,000,0000.917
Mfanyabiashara E anabadilisha token0 10 kwa token1 10.0791,000.990999.0101,000,0001.008

Wafanyabiashara wanapotoa token0 zaidi, thamani ya kulinganisha ya token1 inaongezeka, na kinyume chake, kulingana na usambazaji na mahitaji.

Kufuli

    uint private unlocked = 1;

Kuna aina ya udhaifu wa kiusalama ambao unategemea matumizi mabaya ya uingiaji upya (opens in a new tab). Uniswap inahitaji kuhamisha tokeni za ERC-20 kiholela, ambayo inamaanisha kuita mikataba ya ERC-20 ambayo inaweza kujaribu kutumia vibaya soko la Uniswap linaloiita. Kwa kuwa na kigezo cha unlocked kama sehemu ya mkataba, tunaweza kuzuia kazi kuitwa wakati zinaendelea (ndani ya muamala huo huo).

    modifier lock() {

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

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

Ikiwa unlocked ni sawa na moja, iweke kuwa sifuri. Ikiwa tayari ni sifuri tengua mwito, ifanye ishindwe.

        _;

Katika kirekebishaji _; ni mwito wa kazi asili (pamoja na vigezo vyote). Hapa inamaanisha kuwa mwito wa kazi hutokea tu ikiwa unlocked ilikuwa moja ilipoitwa, na wakati inaendelea thamani ya unlocked ni sifuri.

        unlocked = 1;
    }

Baada ya kazi kuu kurudi, fungua kufuli.

Kazi mbalimbali

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

Kazi hii inawapa wapigaji hali ya sasa ya badilishano. Kumbuka kwamba kazi za Solidity zinaweza kurudisha thamani nyingi (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));

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

Ili kuepuka kulazimika kuingiza kiolesura kwa kazi ya tokeni, tunaunda mwito "kwa mikono" kwa kutumia mojawapo ya kazi za ABI (opens in a new tab).

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

Kuna njia mbili ambazo mwito wa hamisho wa ERC-20 unaweza kuripoti kutofaulu:

  1. Tengua. Ikiwa mwito kwa mkataba wa nje unatengua, basi thamani ya kurudi ya boolean ni false
  2. Maliza kawaida lakini ripoti kutofaulu. Katika hali hiyo bafa ya thamani ya kurudi ina urefu usio wa sifuri, na inaposimbuliwa kama thamani ya boolean inakuwa false

Ikiwa mojawapo ya masharti haya yatatokea, tengua.

Matukio

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

Matukio haya mawili hutolewa wakati mtoa ukwasi anaweka ukwasi (Mint) au kuutoa (Burn). Katika hali yoyote, kiasi cha token0 na token1 ambacho kimewekwa au kutolewa ni sehemu ya tukio, pamoja na utambulisho wa akaunti iliyotuita (sender). Katika kesi ya utoaji, tukio pia linajumuisha lengwa lililopokea tokeni (to), ambalo linaweza lisiwe sawa na mtumaji.

    event Swap(
        address indexed sender,
        uint amount0In,
        uint amount1In,
        uint amount0Out,
        uint amount1Out,
        address indexed to
    );

Tukio hili hutolewa wakati mfanyabiashara anabadilisha tokeni moja kwa nyingine. Tena, mtumaji na marudio inaweza isiwe sawa. Kila tokeni inaweza kutumwa kwa badilishano, au kupokelewa kutoka kwake.

    event Sync(uint112 reserve0, uint112 reserve1);

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

Kazi za Usanidi

Kazi hizi zinapaswa kuitwa mara moja wakati badilishano jipya la jozi linaposanidiwa.

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

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

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

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

Kazi za Ndani za Usasishaji

_update
    // sasisha akiba na, kwenye wito wa kwanza kwa kila kitalu, vilimbikizi vya bei
    function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {

Kazi hii inaitwa kila wakati tokeni zinapowekwa au kutolewa.

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

Ikiwa balance0 au balance1 (uint256) ni kubwa kuliko uint112(-1) (=2^112-1) (kwa hivyo inafurika na kurudi kwenye 0 inapobadilishwa kuwa uint112) kataa kuendelea na _update ili kuzuia kufurika. Kwa tokeni ya kawaida inayoweza kugawanywa katika vipande 10^18, hii inamaanisha kila badilishano lina kikomo cha takriban 5.1*10^15 ya kila tokeni. Kufikia sasa hilo halijawa tatizo.

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

Ikiwa muda uliopita sio sifuri, inamaanisha sisi ni muamala wa kwanza wa badilishano kwenye kitalu hiki. Katika hali hiyo, tunahitaji kusasisha vilimbikizi vya gharama.

            // * haifuriki kamwe, na + kufurika kunahitajika
            price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
            price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
        }

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

Tukioreserve0reserve1mhuri wa mudaKiwango cha ubadilishaji cha ukingoni (reserve1 / reserve0)price0CumulativeLast
Usanidi wa awali1,000.0001,000.0005,0001.0000
Mfanyabiashara A anaweka token0 50 na kupata token1 47.6191,050.000952.3815,0200.90720
Mfanyabiashara B anaweka token0 10 na kupata token1 8.9841,060.000943.3965,0300.89020+10*0.907 = 29.07
Mfanyabiashara C anaweka token0 40 na kupata token1 34.3051,100.000909.0905,1000.82629.07+70*0.890 = 91.37
Mfanyabiashara D anaweka token1 100 na kupata token0 109.01990.9901,009.0905,1101.01891.37+10*0.826 = 99.63
Mfanyabiashara E anaweka token0 10 na kupata token1 10.0791,000.990999.0105,1500.99899.63+40*1.1018 = 143.702

Tuseme tunataka kukokotoa 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. Huu ni wastani katika dakika mbili (sekunde 120). Kwa hivyo bei ya wastani ni 114.632/120 = 0.955.

Ukotoaji huu wa bei ndio sababu tunahitaji kujua ukubwa wa akiba za zamani.

        reserve0 = uint112(balance0);
        reserve1 = uint112(balance1);
        blockTimestampLast = blockTimestamp;
        emit Sync(reserve0, reserve1);
    }

Hatimaye, sasisha vigezo vya kimataifa na utoe tukio la Sync.

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

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

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

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

Soma marudio ya ada ya kiwanda. Ikiwa ni sifuri basi hakuna ada ya itifaki na hakuna haja ya kukokotoa ada hiyo.

        uint _kLast = kLast; // kuokoa gesi

Kigezo cha hali cha kLast kiko kwenye hifadhi, kwa hivyo kitakuwa na thamani kati ya miito tofauti kwa mkataba. Ufikiaji wa hifadhi ni ghali zaidi kuliko ufikiaji wa kumbukumbu tete ambayo hutolewa wakati mwito wa kazi kwa mkataba unapoisha, kwa hivyo tunatumia kigezo cha ndani kuokoa gesi.

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

Watoa ukwasi wanapata mgao wao kwa kuthaminiwa tu kwa tokeni zao za ukwasi. Lakini ada ya itifaki inahitaji tokeni mpya za ukwasi kufuliwa na kutolewa kwa anwani ya feeTo.

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

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

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

Ukotoaji huu mgumu wa ada umeelezwa katika waraka mweupe (opens in a new tab) kwenye ukurasa wa 5. Tunajua kwamba kati ya wakati kLast ilipokokotolewa na sasa hakuna ukwasi ulioongezwa au kutolewa (kwa sababu tunaendesha ukotoaji huu kila wakati ukwasi unapoongezwa au kutolewa, kabla haujabadilika haswa), kwa hivyo mabadiliko yoyote katika reserve0 * reserve1 lazima yatokane na ada za muamala (bila hizo tungeweka reserve0 * reserve1 kuwa thabiti).

                    if (liquidity > 0) _mint(feeTo, liquidity);
                }
            }

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

        } else if (_kLast != 0) {
            kLast = 0;
        }
    }

Ikiwa hakuna ada weka kLast kuwa sifuri (ikiwa haiko hivyo tayari). Wakati mkataba huu uliandikwa 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 hifadhi ambayo hawakuhitaji. Msimbo huu unapata urejeshaji huo inapowezekana.

Kazi Zinazofikika kwa Nje

Kumbuka kwamba ingawa muamala au mkataba wowote unaweza kuita kazi hizi, zimeundwa kuitwa kutoka kwa mkataba wa pembezoni. Ikiwa utaziita moja kwa moja hutaweza kudanganya badilishano la jozi, lakini unaweza kupoteza thamani kupitia kosa.

mint
    // kazi hii ya kiwango cha chini inapaswa kuitwa kutoka kwenye mkataba ambao unafanya ukaguzi muhimu wa usalama
    function mint(address to) external lock returns (uint liquidity) {

Kazi hii inaitwa wakati mtoa ukwasi anaongeza ukwasi kwenye bwawa. Inafua tokeni za ziada za ukwasi kama tuzo. Inapaswa kuitwa kutoka kwa mkataba wa pembezoni ambao unaiita baada ya kuongeza ukwasi katika muamala huo huo (kwa hivyo hakuna mtu mwingine ambaye angeweza kuwasilisha muamala unaodai ukwasi mpya kabla ya mmiliki halali).

        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // kuokoa gesi

Hii ndiyo njia ya kusoma matokeo ya kazi ya Solidity ambayo inarudisha thamani nyingi. Tunatupa thamani za mwisho zilizorudishwa, mhuri wa muda wa kitalu, kwa sababu hatuuhitaji.

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

Pata salio la sasa na uone ni kiasi gani kiliongezwa kwa kila aina ya tokeni.

        bool feeOn = _mintFee(_reserve0, _reserve1);

Kokotoa ada za itifaki za kukusanya, ikiwa zipo, na ufue tokeni za ukwasi ipasavyo. Kwa sababu vigezo kwa _mintFee ni thamani za akiba za zamani, ada inakokotolewa kwa usahihi kulingana tu na mabadiliko ya bwawa kutokana na ada.

        uint _totalSupply = totalSupply; // kuokoa gesi, lazima ifafanuliwe hapa kwa kuwa totalSupply inaweza kusasishwa katika _mintFee
        if (_totalSupply == 0) {
            liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
           _mint(address(0), MINIMUM_LIQUIDITY); // funga daima tokeni za kwanza za MINIMUM_LIQUIDITY

Ikiwa huu ni uwekaji wa kwanza, unda tokeni MINIMUM_LIQUIDITY na uzitume kwa anwani sifuri ili kuzifunga. Haziwezi kukombolewa kamwe, ambayo inamaanisha bwawa halitamwagwa kabisa (hii inatuokoa kutokana na kugawanya kwa sifuri katika baadhi ya maeneo). Thamani ya MINIMUM_LIQUIDITY ni elfu moja, ambayo kwa kuzingatia ERC-20 nyingi zimegawanywa katika vipande vya 10^-18 vya tokeni, kama ETH inavyogawanywa katika Wei, ni 10^-15 kwa thamani ya tokeni moja. Sio gharama kubwa.

Wakati wa uwekaji wa kwanza hatujui thamani ya kulinganisha ya tokeni mbili, kwa hivyo tunazidisha tu kiasi na kuchukua kipeo cha pili, tukichukulia kwamba uwekaji unatupa thamani sawa katika tokeni zote mbili.

Tunaweza kuamini hili kwa sababu ni kwa maslahi ya mweka amana kutoa thamani sawa, ili kuepuka kupoteza thamani kwa usuluhishi. Tuseme kwamba thamani ya tokeni mbili inafanana, lakini mweka amana wetu aliweka mara nne zaidi ya Token1 kuliko Token0. Mfanyabiashara anaweza kutumia ukweli kwamba badilishano la jozi linafikiri kwamba Token0 ina thamani zaidi ili kutoa thamani kutoka kwake.

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

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

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

Kwa kila uwekaji unaofuata tayari tunajua kiwango cha ubadilishaji kati ya rasilimali mbili, na tunatarajia watoa ukwasi kutoa thamani sawa katika zote mbili. Ikiwa hawatafanya hivyo, tunawapa tokeni za ukwasi kulingana na thamani ndogo waliyotoa kama adhabu.

Iwe ni uwekaji wa awali au unaofuata, idadi ya tokeni za ukwasi tunazotoa ni sawa na kipeo cha pili cha mabadiliko katika reserve0*reserve1 na thamani ya tokeni ya ukwasi haibadiliki (isipokuwa tupate uwekaji ambao hauna thamani sawa za aina zote mbili, ambapo "faini" inasambazwa). Hapa kuna mfano mwingine na tokeni mbili ambazo zina thamani sawa, na uwekaji mzuri tatu na moja mbaya (uwekaji wa aina moja tu ya tokeni, kwa hivyo haitoi tokeni zozote za ukwasi).

Tukioreserve0reserve1reserve0 * reserve1Thamani ya bwawa (reserve0 + reserve1)Tokeni za ukwasi zilizofufuliwa kwa uwekaji huuJumla ya tokeni za ukwasithamani ya kila tokeni ya ukwasi
Usanidi wa awali8.0008.0006416.000882.000
Weka nne za kila aina12.00012.00014424.0004122.000
Weka mbili za kila aina14.00014.00019628.0002142.000
Uwekaji wa thamani isiyo sawa18.00014.00025232.000014~2.286
Baada ya usuluhishi~15.874~15.874252~31.748014~2.267
        }
        require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
        _mint(to, liquidity);

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


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

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

burn
    // kazi hii ya kiwango cha chini inapaswa kuitwa kutoka kwenye mkataba ambao unafanya ukaguzi muhimu wa usalama
    function burn(address to) external lock returns (uint amount0, uint amount1) {

Kazi hii inaitwa wakati ukwasi unatolewa na tokeni zinazofaa za ukwasi zinahitaji kuteketezwa. Inapaswa pia kuitwa kutoka kwa akaunti ya pembezoni.

        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // kuokoa gesi
        address _token0 = token0;                                // kuokoa gesi
        address _token1 = token1;                                // kuokoa gesi
        uint balance0 = IERC20(_token0).balanceOf(address(this));
        uint balance1 = IERC20(_token1).balanceOf(address(this));
        uint liquidity = balanceOf[address(this)];

Mkataba wa pembezoni ulihamisha ukwasi wa kuteketezwa kwa mkataba huu kabla ya mwito. Kwa njia hiyo tunajua ni kiasi gani cha ukwasi cha kuteketeza, na tunaweza kuhakikisha kwamba unateketezwa.

        bool feeOn = _mintFee(_reserve0, _reserve1);
        uint _totalSupply = totalSupply; // kuokoa gesi, lazima ifafanuliwe hapa kwa kuwa totalSupply inaweza kusasishwa katika _mintFee
        amount0 = liquidity.mul(balance0) / _totalSupply; // kutumia salio kunahakikisha usambazaji wa pro-rata
        amount1 = liquidity.mul(balance1) / _totalSupply; // kutumia salio kunahakikisha usambazaji wa pro-rata
        require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');

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

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

swap
    // kazi hii ya kiwango cha chini inapaswa kuitwa kutoka kwenye mkataba ambao unafanya ukaguzi muhimu wa usalama
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {

Kazi hii pia inapaswa kuitwa kutoka kwa mkataba wa pembezoni.

        require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // kuokoa gesi
        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

        uint balance0;
        uint balance1;
        { // upeo wa _token{0,1}, inaepuka makosa ya staki kuwa na kina sana

Vigezo vya ndani vinaweza kuhifadhiwa kwenye kumbukumbu au, ikiwa sio vingi sana, moja kwa moja kwenye staki. Ikiwa tunaweza kupunguza idadi ili tutumie staki tunatumia gesi kidogo. Kwa maelezo zaidi tazama waraka wa manjano, vipimo rasmi vya Ethereum (opens in a new tab), uk. 26, mlinganyo 298.

            address _token0 = token0;
            address _token1 = token1;
            require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
            if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // hamisha tokeni kwa matumaini
            if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // hamisha tokeni kwa matumaini

Hamisho hili lina matumaini, kwa sababu tunahamisha kabla ya kuwa na uhakika masharti yote yametimizwa. Hii ni Sawa katika Ethereum kwa sababu ikiwa masharti hayatatimizwa baadaye katika mwito tunatengua kutoka kwake na mabadiliko yoyote yaliyoundwa.

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

Mjulishe mpokeaji kuhusu badilishano ikiwa imeombwa.

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

Pata salio la sasa. Mkataba wa pembezoni unatutumia tokeni kabla ya kutuita kwa badilishano. Hii inafanya iwe rahisi kwa mkataba kuangalia kwamba haudanganywi, ukaguzi ambao lazima ufanyike katika mkataba wa msingi (kwa sababu tunaweza kuitwa na vyombo vingine isipokuwa mkataba wetu wa pembezoni).

        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');
        { // upeo wa reserve{0,1}Adjusted, inaepuka makosa ya staki kuwa na kina sana
            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');

Huu ni ukaguzi wa kiakili ili kuhakikisha hatupotezi kutokana na badilishano. Hakuna hali ambayo badilishano linapaswa kupunguza reserve0*reserve1. Hapa pia ndipo tunahakikisha ada ya 0.3% inatumwa kwenye badilishano; kabla ya kukagua kiakili thamani ya K, tunazidisha salio zote mbili kwa 1000 ikitolewa na kiasi kilichozidishwa kwa 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.

        }

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

Sasisha reserve0 na reserve1, na ikihitajika vilimbikizi vya bei na mhuri wa muda na utoe tukio.

Usawazishaji au Skim

Inawezekana kwa salio halisi kutoka nje ya usawazishaji na akiba ambazo badilishano la jozi linafikiri linazo. Hakuna njia ya kutoa tokeni bila idhini ya mkataba, lakini uwekaji ni jambo tofauti. Akaunti inaweza kuhamisha tokeni kwa badilishano bila kuita mint au swap.

Katika hali hiyo kuna suluhisho mbili:

  • sync, sasisha akiba kwa salio la sasa
  • skim, toa kiasi cha ziada. Kumbuka kwamba akaunti yoyote inaruhusiwa kuita skim kwa sababu hatujui nani aliweka tokeni. Taarifa hii inatolewa katika tukio, lakini matukio hayafikiki kutoka kwa mnyororo wa vitalu.

UniswapV2Factory.sol

Mkataba huu (opens in a new tab) unaunda mabadilishano ya jozi.

pragma solidity =0.5.16;

import './interfaces/IUniswapV2Factory.sol';
import './UniswapV2Pair.sol';

contract UniswapV2Factory is IUniswapV2Factory {
    address public feeTo;
    address public feeToSetter;

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

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

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

Ya kwanza, getPair, ni ramani inayotambua mkataba wa badilishano la jozi kulingana na tokeni mbili za ERC-20 inazobadilishana. Tokeni za ERC-20 zinatambuliwa na anwani za mikataba inayoitekeleza, kwa hivyo funguo na thamani zote ni anwani. Ili kupata anwani ya badilishano la jozi inayokuruhusu kubadilisha kutoka tokenA hadi tokenB, unatumia getPair[<tokenA address>][<tokenB address>] (au kinyume chake).

Kigezo cha pili, allPairs, ni safu inayojumuisha anwani zote za mabadilishano ya jozi yaliyoundwa na kiwanda hiki. Katika Ethereum huwezi kurudia juu ya maudhui ya ramani, au kupata orodha ya funguo zote, kwa hivyo kigezo hiki ndiyo njia pekee ya kujua ni mabadilishano yapi 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 huhitaji hilo.

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

Tukio hili hutolewa wakati badilishano jipya la jozi linaundwa. Linajumuisha anwani za tokeni, anwani ya badilishano la jozi, na jumla ya idadi ya mabadilishano yanayosimamiwa na kiwanda.

    constructor(address _feeToSetter) public {
        feeToSetter = _feeToSetter;
    }

Kitu pekee ambacho konstrukta inafanya ni kubainisha feeToSetter. Viwanda huanza bila ada, na feeSetter pekee ndiye anayeweza kubadilisha hilo.

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

Kazi hii inarudisha idadi ya jozi za badilishano.

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

Hii ndiyo kazi kuu ya kiwanda, kuunda badilishano la jozi kati ya tokeni mbili za ERC-20. Kumbuka kwamba mtu yeyote anaweza kuita kazi hii. Huhitaji ruhusa kutoka kwa Uniswap kuunda badilishano jipya la jozi.

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

Tunataka anwani ya badilishano jipya iwe ya kubainika, ili iweze kukokotolewa mapema nje ya mnyororo (hii inaweza kuwa muhimu kwa miamala ya tabaka la 2 (l2)). Ili kufanya hivi tunahitaji kuwa na mpangilio thabiti wa anwani za tokeni, bila kujali mpangilio ambao tumezipokea, kwa hivyo tunazipanga hapa.

        require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
        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 zaidi ya bwawa moja la ukwasi kwa kila jozi ya tokeni. Ikiwa tayari kuna badilishano, hakuna haja ya kuunda lingine kwa jozi hiyo hiyo.

        bytes memory bytecode = type(UniswapV2Pair).creationCode;

Ili kuunda mkataba mpya tunahitaji msimbo unaouunda (kazi ya konstrukta na msimbo unaoandika kwenye kumbukumbu msimbo wa baiti wa EVM wa mkataba halisi). Kawaida katika Solidity tunatumia tu addr = new <name of contract>(<constructor parameters>) na kikusanyaji kinashughulikia kila kitu kwa ajili yetu, lakini ili kuwa na anwani ya mkataba inayobainika tunahitaji kutumia msimbo wa operesheni wa CREATE2 (opens in a new tab). Wakati msimbo huu uliandikwa msimbo huo wa operesheni ulikuwa bado hautumiki 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).

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

Wakati msimbo wa operesheni bado hautumiki na Solidity tunaweza kuuita kwa kutumia mkusanyiko wa ndani (opens in a new tab).

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

Ita kazi ya initialize ili kuambia badilishano jipya ni tokeni gani mbili inabadilishana.

        getPair[token0][token1] = pair;
        getPair[token1][token0] = pair; // jaza ramani katika uelekeo wa kinyume
        allPairs.push(pair);
        emit PairCreated(token0, token1, pair, allPairs.length);
    }

Hifadhi taarifa mpya ya jozi katika vigezo vya hali na utoe tukio ili kuujulisha ulimwengu kuhusu badilishano jipya la jozi.

Kazi hizi mbili zinaruhusu feeSetter kudhibiti mpokeaji wa ada (ikiwa yupo), na kubadilisha feeSetter kwa anwani mpya.

UniswapV2ERC20.sol

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

Miamala kwenye Ethereum inagharimu Etha (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 ya meta (opens in a new tab). Mmiliki wa tokeni anatia sahihi muamala unaoruhusu mtu mwingine kutoa tokeni nje ya mnyororo na kuutuma kwa kutumia Mtandao kwa mpokeaji. Mpokeaji, ambaye ana ETH, kisha anawasilisha kibali kwa niaba ya mmiliki.

    bytes32 public DOMAIN_SEPARATOR;
    // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;

Heshi hii ni kitambulisho cha aina ya muamala (opens in a new tab). Ya pekee tunayounga mkono hapa ni Permit na vigezo hivi.

    mapping(address => uint) public nonces;

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

    constructor() public {
        uint chainId;
        assembly {
            chainId := chainid
        }

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 kwamba katika toleo la sasa la Yul lazima utumie chainid(), sio chainid.

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

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

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

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

Usikubali miamala baada ya tarehe ya mwisho.

        bytes32 digest = keccak256(
            abi.encodePacked(
                '\x19\x01',
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            )
        );

abi.encodePacked(...) ni ujumbe tunaotarajia kupata. Tunajua nonsi inapaswa kuwa nini, kwa hivyo hakuna haja ya sisi kuipata kama kigezo.

Kanuni ya sahihi ya Ethereum inatarajia kupata biti 256 za kutia sahihi, kwa hivyo tunatumia kazi ya heshi ya keccak256.

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

Kutoka kwa muhtasari na sahihi tunaweza kupata anwani iliyotia sahihi kwa kutumia ecrecover (opens in a new tab).

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

Ikiwa kila kitu kiko Sawa, chukulia hii kama idhinisha ya ERC-20 (opens in a new tab).

Mikataba ya Pembezoni

Mikataba ya pembezoni ni API (kiolesura cha programu ya matumizi) cha Uniswap. Inapatikana kwa miito ya nje, iwe kutoka kwa mikataba mingine au programu zilizogatuliwa. Unaweza kuita mikataba mikuu moja kwa moja, lakini hiyo ni ngumu zaidi na unaweza kupoteza thamani ukifanya kosa. Mikataba mikuu ina majaribio tu ya kuhakikisha haidanganywi, si ukaguzi wa usahihi kwa mtu mwingine yeyote. Hiyo ipo pembezoni ili iweze kusasishwa inapohitajika.

UniswapV2Router01.sol

Mkataba huu (opens in a new tab) una matatizo, na hupaswi kutumiwa tena (opens in a new tab). Kwa bahati nzuri, mikataba ya pembezoni haina hali na haishikilii rasilimali zozote, kwa hivyo ni rahisi kuiondoa na kupendekeza watu watumie mbadala wake, UniswapV2Router02, badala yake.

UniswapV2Router02.sol

Katika hali nyingi ungetumia Uniswap kupitia mkataba huu (opens in a new tab). Unaweza kuona jinsi ya kuutumia hapa (opens in a new tab).

Mengi ya haya tulikutana nayo hapo awali, au yako wazi kabisa. Upekee mmoja ni IWETH.sol. Uniswap v2 inaruhusu mabadilishano kwa jozi yoyote ya tokeni za ERC-20, lakini Etha (ETH) yenyewe si tokeni ya ERC-20. Ilitangulia 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 kwenye mkataba huu, na inakufua kiasi sawa cha WETH. Au unaweza kuteketeza WETH, na kupata ETH nyuma.

contract UniswapV2Router02 is IUniswapV2Router02 {
    using SafeMath for uint;

    address public immutable override factory;
    address public immutable override WETH;

Ruta inahitaji kujua ni kiwanda gani cha kutumia, na kwa miamala inayohitaji WETH ni mkataba gani wa WETH wa kutumia. Thamani hizi ni zisizobadilika (opens in a new tab), ikimaanisha zinaweza tu kuwekwa kwenye konstrukta. Hii inawapa watumiaji ujasiri kwamba hakuna mtu ambaye angeweza kuzibadilisha ili kuelekeza kwenye mikataba isiyo ya uaminifu.

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

Kirekebishaji hiki kinahakikisha kwamba miamala yenye kikomo cha muda ("fanya X kabla ya muda Y ikiwa unaweza") haifanyiki baada ya kikomo chake cha muda.

    constructor(address _factory, address _WETH) public {
        factory = _factory;
        WETH = _WETH;
    }

Konstrukta inaweka tu vigezo vya hali isiyobadilika.

    receive() external payable {
        assert(msg.sender == WETH); // kubali ETH pekee kupitia fallback kutoka kwenye mkataba wa WETH
    }

Kazi hii inaitwa tunapokomboa tokeni kutoka kwenye mkataba wa WETH kurudi kwenye ETH. Ni mkataba wa WETH pekee tunaoutumia ndio ulioidhinishwa kufanya hivyo.

Ongeza Ukwasi

Kazi hizi zinaongeza tokeni kwenye mabadilishano ya jozi, ambayo huongeza bwawa la ukwasi.


    // **** ONGEZA UKWASI ****
    function _addLiquidity(

Kazi hii inatumika kukokotoa kiasi cha tokeni za A na B ambazo zinapaswa kuwekwa kwenye mabadilishano ya jozi.

        address tokenA,
        address tokenB,

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

        uint amountADesired,
        uint amountBDesired,

Hivi ni viwango ambavyo mtoa ukwasi anataka kuweka. Pia ni viwango vya juu zaidi vya A na B vya kuwekwa.

        uint amountAMin,
        uint amountBMin

Hivi ni viwango vya chini vinavyokubalika kuweka. Ikiwa muamala hauwezi kufanyika kwa viwango hivi au zaidi, tengua kutoka kwake. Ikiwa hutaki kipengele hiki, taja tu sifuri.

Watoa ukwasi hutaja kiwango cha chini, kwa kawaida, kwa sababu wanataka kuweka kikomo cha muamala kwenye kiwango cha ubadilishaji ambacho kiko karibu na kile cha sasa. Ikiwa kiwango cha ubadilishaji kinabadilika sana inaweza kumaanisha habari zinazobadilisha thamani za msingi, na wanataka kuamua wenyewe nini cha kufanya.

Kwa mfano, fikiria hali ambapo kiwango cha ubadilishaji ni moja kwa moja na mtoa ukwasi anataja thamani hizi:

KigezoThamani
amountADesired1000
amountBDesired1000
amountAMin900
amountBMin800

Ilimradi kiwango cha ubadilishaji kinabaki kati ya 0.9 na 1.25, muamala unafanyika. Ikiwa kiwango cha ubadilishaji kinatoka nje ya safu hiyo, muamala unafutwa.

Sababu ya tahadhari hii ni kwamba miamala si ya papo hapo, unaiwasilisha na hatimaye mthibitishaji ataijumuisha kwenye kitalu (isipokuwa bei ya gesi yako ni ya chini sana, ambapo utahitaji kuwasilisha muamala mwingine wenye nonsi sawa na bei ya gesi ya juu zaidi ili kuufunika). Huwezi kudhibiti kile kinachotokea wakati wa kipindi kati ya uwasilishaji na ujumuishaji.

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

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

        // tengeneza jozi kama bado haipo
        if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
            IUniswapV2Factory(factory).createPair(tokenA, tokenB);
        }

Ikiwa hakuna mabadilishano kwa jozi hii ya tokeni bado, iunde.

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

Pata akiba za sasa kwenye jozi.

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

Ikiwa akiba za sasa ni tupu basi haya ni mabadilishano mapya ya jozi. Viwango vya kuwekwa vinapaswa kuwa sawa kabisa na vile ambavyo mtoa ukwasi anataka kutoa.

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

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

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

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

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

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

Tukiweka yote pamoja tunapata grafu hii. Chukulia unajaribu kuweka tokeni elfu moja za A (mstari wa bluu) na tokeni elfu moja za B (mstari mwekundu). Mhimili wa x ni kiwango cha ubadilishaji, 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 ya A) kwa hivyo unaweka tokeni elfu moja za B, lakini tokeni 500 tu za A. Ikiwa x=0.5, hali inageuzwa, tokeni elfu moja za A na tokeni mia tano za B.

Graph

Unaweza kuweka ukwasi moja kwa moja kwenye mkataba mkuu (kwa kutumia UniswapV2Pair::mint (opens in a new tab)), lakini mkataba mkuu unakagua tu kwamba haudanganywi wenyewe, kwa hivyo unajiweka kwenye hatari ya kupoteza thamani ikiwa kiwango cha ubadilishaji kitabadilika kati ya wakati unapowasilisha muamala wako na wakati unapotekelezwa. Ikiwa unatumia mkataba wa pembezoni, inakokotoa kiwango unachopaswa kuweka na kukiweka mara moja, kwa hivyo kiwango cha ubadilishaji hakibadiliki na hupotezi chochote.

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

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

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

Tunakokotoa viwango vya kuweka haswa na kisha kutafuta anwani ya bwawa la ukwasi. Ili kuokoa gesi hatufanyi hivi kwa kuuliza kiwanda, bali kwa kutumia kazi ya maktaba pairFor (tazama hapa chini kwenye maktaba)

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

Hamisha viwango sahihi vya tokeni kutoka kwa mtumiaji hadi kwenye mabadilishano ya jozi.

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

Kwa malipo ipe anwani ya to tokeni za ukwasi kwa umiliki wa sehemu wa bwawa. Kazi ya mint ya mkataba mkuu inaona ni tokeni ngapi za ziada ilizonazo (ikilinganishwa na ilivyokuwa nayo mara ya mwisho ukwasi ulipobadilika) na inafua ukwasi ipasavyo.

    function addLiquidityETH(
        address token,
        uint amountTokenDesired,

Wakati mtoa ukwasi anataka kutoa ukwasi kwenye mabadilishano ya jozi ya Tokeni/ETH, kuna tofauti chache. Mkataba unashughulikia kufunga ETH kwa ajili ya mtoa ukwasi. Hakuna haja ya kutaja ni ETH ngapi mtumiaji anataka kuweka, kwa sababu mtumiaji anazituma tu pamoja na muamala (kiwango kinapatikana katika msg.value).

Ili kuweka ETH mkataba kwanza unaifunga kuwa WETH na kisha kuhamisha WETH kwenye jozi. Kumbuka kwamba hamisho limefungwa katika assert. Hii inamaanisha kwamba ikiwa hamisho litashindwa mwito huu wa mkataba pia unashindwa, na kwa hivyo kufunga hakufanyiki kweli.

        liquidity = IUniswapV2Pair(pair).mint(to);
        // rejesha Etha ya vumbi, kama ipo
        if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
    }

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

Ondoa Ukwasi

Kazi hizi zitaondoa ukwasi na kumlipa mtoa ukwasi.

Hali rahisi zaidi ya kuondoa ukwasi. Kuna kiwango cha chini cha kila tokeni ambacho mtoa ukwasi anakubali kupokea, na lazima ifanyike kabla ya tarehe ya mwisho.

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

Kazi ya burn ya mkataba mkuu inashughulikia kumlipa mtumiaji tokeni zake.

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

Wakati kazi inarudisha thamani nyingi, lakini tunavutiwa na baadhi tu, hivi ndivyo tunavyopata thamani hizo pekee. Ni nafuu kiasi kwa upande wa gesi kuliko kusoma thamani na kutoitumia kamwe.

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

Tafsiri viwango kutoka jinsi mkataba mkuu unavyozirudisha (tokeni ya anwani ya chini kwanza) hadi jinsi mtumiaji anavyozitarajia (kulingana na tokenA na tokenB).

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

Ni Sawa kufanya hamisho kwanza na kisha kuthibitisha kuwa ni halali, kwa sababu ikiwa sivyo tutatengua mabadiliko yote ya hali.

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

Kazi hizi hupitisha miamala-meta ili kuruhusu watumiaji wasio na etha kutoa kutoka kwenye bwawa, kwa kutumia utaratibu wa kibali.

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

Kazi ya mwisho inachanganya ada za uhifadhi na miamala-meta.

Biashara

    // **** BADILISHANO ****
    // inahitaji kiasi cha awali kiwe tayari kimetumwa kwenye jozi ya kwanza
    function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {

Kazi hii inafanya uchakataji wa ndani ambao unahitajika kwa kazi ambazo zinawekwa wazi kwa wafanyabiashara.

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

Ninapoandika haya kuna tokeni 388,160 za ERC-20 (opens in a new tab). Kama kungekuwa na mabadilishano ya jozi kwa kila jozi ya tokeni, ingekuwa zaidi ya mabadilishano ya jozi bilioni 150. Mnyororo mzima, kwa sasa, una 0.1% tu ya idadi hiyo ya akaunti (opens in a new tab). Badala yake, kazi za badilishano zinaunga mkono dhana ya njia. Mfanyabiashara anaweza kubadilisha A kwa B, B kwa C, na C kwa D, kwa hivyo hakuna haja ya mabadilishano ya moja kwa moja ya jozi ya A-D.

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

  1. Hali ya awali
  2. Mfanyabiashara anauza tokeni 24.695 za A na kupata tokeni 25.305 za B.
  3. Mfanyabiashara anauza tokeni 24.695 za B kwa tokeni 25.305 za C, akiweka takriban tokeni 0.61 za B kama faida.
  4. Kisha mfanyabiashara anauza tokeni 24.695 za C kwa tokeni 25.305 za A, akiweka takriban tokeni 0.61 za C kama faida. Mfanyabiashara pia ana tokeni 0.61 za ziada za A (zile 25.305 ambazo mfanyabiashara anamalizia nazo, ukiondoa uwekezaji wa awali wa 24.695).
HatuaMabadilishano ya A-BMabadilishano ya B-CMabadilishano ya 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
            (address input, address output) = (path[i], path[i + 1]);
            (address token0,) = UniswapV2Library.sortTokens(input, output);
            uint amountOut = amounts[i + 1];

Pata jozi tunayoshughulikia kwa sasa, ipange (kwa matumizi na jozi) na upate kiwango cha pato kinachotarajiwa.

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

Pata viwango vya pato vinavyotarajiwa, vilivyopangwa jinsi mabadilishano ya jozi yanavyotarajia viwe.

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

Je, haya ni mabadilishano ya mwisho? Ikiwa ndivyo, tuma tokeni zilizopokelewa kwa biashara kwenye kituo. Ikiwa sivyo, itume kwenye mabadilishano ya jozi inayofuata.


            IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                amount0Out, amount1Out, to, new bytes(0)
            );
        }
    }

Kwa kweli ita mabadilishano ya jozi ili kubadilishana tokeni. Hatuhitaji mwito wa kurudi ili kuambiwa kuhusu mabadilishano, kwa hivyo hatutumi baiti zozote kwenye uwanja huo.

    function swapExactTokensForTokens(

Kazi hii inatumika moja kwa moja na wafanyabiashara kubadilishana tokeni moja kwa nyingine.

        uint amountIn,
        uint amountOutMin,
        address[] calldata path,

Kigezo hiki kina anwani za mikataba ya ERC-20. Kama ilivyoelezwa hapo juu, huu ni mfuatano kwa sababu unaweza kuhitaji kupitia mabadilishano kadhaa ya jozi ili kutoka kwenye rasilimali uliyonayo hadi kwenye rasilimali unayotaka.

Kigezo cha kazi katika Solidity kinaweza kuhifadhiwa ama katika memory au calldata. Ikiwa kazi 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 kwenye data za mwito. Ikiwa kazi inaitwa kwa ndani, kama _swap hapo juu, basi vigezo vinapaswa kuhifadhiwa katika memory. Kutoka kwa mtazamo wa mkataba ulioitwa calldata inasomeka tu.

Kwa aina za skali kama vile uint au address kikusanyaji kinashughulikia chaguo la uhifadhi kwa ajili yetu, lakini kwa mifuatano, ambayo ni mirefu na ya gharama zaidi, tunataja aina ya uhifadhi itakayotumika.

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

Thamani za kurudisha kila wakati zinarudishwa kwenye kumbukumbu.

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

Kokotoa kiwango cha kununuliwa katika kila badilishano. Ikiwa matokeo ni chini ya kiwango cha chini ambacho mfanyabiashara yuko tayari kukubali, tengua kutoka kwenye muamala.

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

Hatimaye, hamisha tokeni ya awali ya ERC-20 kwenye akaunti kwa ajili ya mabadilishano ya kwanza ya jozi na uite _swap. Haya yote yanafanyika katika muamala ule ule, kwa hivyo mabadilishano ya jozi yanajua kwamba tokeni zozote zisizotarajiwa ni sehemu ya hamisho hili.

Kazi iliyotangulia, swapTokensForTokens, inaruhusu mfanyabiashara kutaja idadi kamili ya tokeni za kuingiza ambazo yuko tayari kutoa na idadi ya chini ya tokeni za pato ambazo yuko tayari kupokea kwa malipo. Kazi hii inafanya badilishano la kinyume, inamruhusu mfanyabiashara kutaja idadi ya tokeni za pato anazotaka, na idadi ya juu zaidi ya tokeni za kuingiza ambazo yuko tayari kulipia.

Katika visa vyote viwili, mfanyabiashara anapaswa kuupa mkataba huu wa pembezoni kwanza kibali ili kuuruhusu kuzihamisha.

Tofauti hizi nne zote zinahusisha biashara kati ya ETH na tokeni. Tofauti pekee ni kwamba tunapokea ETH kutoka kwa mfanyabiashara na kuitumia kufua WETH, au tunapokea WETH kutoka kwenye mabadilishano ya mwisho kwenye njia na kuiteketeza, tukimtumia mfanyabiashara ETH inayotokana.

    // **** BADILISHANO (inasaidia tokeni za ada-kwa-hamisho) ****
    // inahitaji kiasi cha awali kiwe tayari kimetumwa kwenye jozi ya kwanza
    function _swapSupportingFeeOnTransferTokens(address[] memory path, address _to) internal virtual {

Hii ni kazi ya ndani ya kubadilishana tokeni ambazo zina ada za hamisho au uhifadhi ili kutatua (suala hili (opens in a new tab)).

Kwa sababu ya ada za hamisho hatuwezi kutegemea kazi ya getAmountsOut kutuambia ni kiasi gani tunapata kutoka kwa kila hamisho (kama tunavyofanya kabla ya kuita _swap ya asili). Badala yake tunapaswa kuhamisha kwanza na kisha kuona ni tokeni ngapi tulipata nyuma.

Kumbuka: Kinadharia tungeweza tu kutumia kazi hii badala ya _swap, lakini katika visa fulani (kwa mfano, ikiwa hamisho linaishia kutenguliwa kwa sababu hakuna kiasi cha kutosha mwishoni ili kufikia kiwango cha chini kinachohitajika) hiyo ingeishia kugharimu gesi zaidi. Tokeni za ada ya hamisho ni nadra sana, kwa hivyo ingawa tunahitaji kuzishughulikia hakuna haja ya mabadilishano yote kudhani yanapitia angalau moja wapo.

Hizi ni tofauti zile zile zinazotumika kwa tokeni za kawaida, lakini zinaita _swapSupportingFeeOnTransferTokens badala yake.

Kazi hizi ni proksi tu zinazoita kazi za UniswapV2Library.

UniswapV2Migrator.sol

Mkataba huu ulitumika kuhamisha mabadilishano kutoka v1 ya zamani hadi v2. Kwa kuwa sasa yamehamishwa, hauhusiki 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 kazi za hisabati ambazo kwa kawaida hazihitajiki katika msimbo wa Solidity, kwa hivyo si sehemu ya lugha.

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

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

Pata makadirio ya karibu zaidi, wastani wa makadirio ya awali na nambari ambayo tunajaribu kutafuta kipeo chake cha pili ikigawanywa na makadirio ya awali. Rudia hadi makadirio mapya yasiwe chini ya yale yaliyopo. Kwa maelezo zaidi, tazama hapa (opens in a new tab).

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

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

        }
    }
}

Sehemu za Nukta Zisizobadilika (UQ112x112)

Maktaba hii inashughulikia sehemu, ambazo kwa kawaida si sehemu ya hesabu za Ethereum. Inafanya hivi kwa kusimba nambari x kama x*2^112. Hii inaturuhusu kutumia misimbo ya operesheni ya asili ya kujumlisha na kutoa bila mabadiliko.

Q112 ni usimbaji wa moja.

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

Kwa sababu y ni uint112, kiwango chake cha juu zaidi kinaweza kuwa 2^112-1. Nambari hiyo bado inaweza kusimbwa kama UQ112x112.

    // gawanya UQ112x112 kwa uint112, ikirejesha UQ112x112
    function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
        z = x / uint224(y);
    }
}

Ikiwa tutagawa thamani mbili za UQ112x112, matokeo hayazidishwi tena na 2^112. Kwa hivyo badala yake tunachukua nambari kamili kwa asili. Tungehitaji kutumia mbinu sawa kufanya kuzidisha, lakini hatuhitaji kufanya kuzidisha kwa thamani za UQ112x112.

UniswapV2Library

Maktaba hii inatumika tu na mikataba ya pembezoni

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 mabadilishano mawili badala ya moja.

Kazi hii inakokotoa anwani ya ubadilishanaji wa jozi kwa tokeni mbili. Mkataba huu unaundwa kwa kutumia msimbo wa operesheni wa CREATE2 (opens in a new tab), kwa hivyo tunaweza kukokotoa anwani kwa kutumia algoriti sawa ikiwa tunajua vigezo inavyotumia. Hii ni nafuu sana kuliko kuuliza kiwanda, na

    // inaleta na kupanga akiba kwa ajili ya jozi
    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);
    }

Kazi hii inarejesha akiba ya tokeni mbili ambazo ubadilishanaji wa jozi unazo. Kumbuka kwamba inaweza kupokea tokeni kwa mpangilio wowote, na kuzipanga kwa matumizi ya ndani.

    // ikizingatiwa kiasi fulani cha rasilimali na akiba za jozi, inarejesha kiasi sawa cha rasilimali nyingine
    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;
    }

Kazi hii inakupa kiasi cha tokeni B utakachopata kwa kubadilishana na tokeni A ikiwa hakuna ada inayohusika. Ukokotoaji huu unazingatia kwamba hamisho hubadilisha kiwango cha ubadilishaji.

    // ikizingatiwa kiasi cha ingizo cha rasilimali na akiba za jozi, inarejesha kiasi cha juu zaidi cha zao la rasilimali nyingine
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {

Kazi 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 cha chini. Kazi hii inakokotoa kiasi baada ya ada ya ubadilishaji.


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

    // ikizingatiwa kiasi cha zao cha rasilimali na akiba za jozi, inarejesha kiasi kinachohitajika cha ingizo cha rasilimali nyingine
    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);
    }

Kazi hii inafanya takriban kitu kile kile, lakini inapata kiasi cha pato na kutoa ingizo.

Kazi hizi mbili zinashughulikia kutambua thamani wakati ni lazima kupitia mabadilishano kadhaa ya jozi.

Msaidizi wa Hamisho

Maktaba hii (opens in a new tab) inaongeza ukaguzi wa mafanikio karibu na mahamisho ya ERC-20 na Ethereum ili kushughulikia tengua na urejeshaji wa thamani ya false kwa njia sawa.

Tunaweza kuita mkataba tofauti kwa moja ya njia mbili:

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

Kwa ajili ya utangamano wa nyuma na tokeni zilizoundwa kabla ya kiwango cha ERC-20, mwito wa ERC-20 unaweza kushindwa ama kwa kutengua (ambapo success ni false) au kwa kufanikiwa na kurejesha thamani ya false (ambapo kuna data ya pato, na ukiisimba kama boolean unapata false).

Kazi hii inatekeleza utendaji wa hamisho wa ERC-20 (opens in a new tab), ambao unaruhusu akaunti kutumia kibali kilichotolewa na akaunti tofauti.

Kazi hii inatekeleza utendaji wa transferFrom wa ERC-20 (opens in a new tab), ambao unaruhusu akaunti kutumia kibali kilichotolewa na akaunti tofauti.


    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }
}

Kazi hii inahamisha Etha kwenye akaunti. Mwito wowote kwa mkataba tofauti unaweza kujaribu kutuma Etha. Kwa sababu hatuhitaji kuita kazi yoyote, hatutumi data yoyote na mwito huo.

Hitimisho

Hii ni makala ndefu ya takriban kurasa 50. Kama umefika hapa, hongera! Tunatumai kufikia sasa umeelewa mambo ya kuzingatia katika kuandika programu halisi (tofauti na programu fupi za mfano) na una uwezo mzuri zaidi wa kuandika mikataba kwa ajili ya matumizi yako mwenyewe.

Sasa nenda ukaandike kitu cha manufaa na utushangaze.

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