<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Uniswap core源碼學習

      uniswap的core代碼分為兩部分,FactoryPair,其中Factory是工廠合約,主要用來創(chuàng)建交易對,而Pair就是交易對合約,控制LP的mintburn,以及用戶的swap交易。

      Factory

      首先來看一下Factory合約,定義了四個變量:

        address public feeTo;
          address public feeToSetter;
      
          mapping(address => mapping(address => address)) public getPair;
          address[] public allPairs;
          
          constructor(address _feeToSetter) public {
      	    feeToSetter = _feeToSetter;
      	  }
      

      feeTofeeToSetter負責協(xié)議手續(xù)費的去向控制,構(gòu)造合約的時候需要設(shè)置feeToSetter,做好權(quán)限控制。

      getPairallPairs用于記錄所有的流動性交易對以及映射關(guān)系。

        function setFeeTo(address _feeTo) external {
            require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
            feeTo = _feeTo;
        }
      
        function setFeeToSetter(address _feeToSetter) external {
            require(msg.sender == feeToSetter, 'UniswapV2: FORBIDDEN');
            feeToSetter = _feeToSetter;
        }
      }
      

      提供了兩個可以用來修改手續(xù)費setterto地址的方法。

      最重要的核心就是下面的createPair,用于創(chuàng)建交易對。

        function createPair(address tokenA, address tokenB) external returns (address pair) {
            require(tokenA != tokenB, 'UniswapV2: IDENTICAL_ADDRESSES');
            (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
            require(token0 != address(0), 'UniswapV2: ZERO_ADDRESS');
            require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS'); // single check is sufficient
            bytes memory bytecode = type(UniswapV2Pair).creationCode;
            bytes32 salt = keccak256(abi.encodePacked(token0, token1));
            assembly {
                pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
            }
            IUniswapV2Pair(pair).initialize(token0, token1);
            getPair[token0][token1] = pair;
            getPair[token1][token0] = pair; // populate mapping in the reverse direction
            allPairs.push(pair);
            emit PairCreated(token0, token1, pair, allPairs.length);
        }
      

      權(quán)限控制

      第一部分的代碼寫了三個require:

      • 交易對兩端token不可相同
      • 交易對token不可為零地址
      • 交易對還未創(chuàng)建

      可以看到這里對tokenAtokenB做了一個排序,這是為了保證唯一性,不論傳入什么樣順序的交易對,都能輸出一樣的結(jié)果。

      因為token0小于token1,所以在檢查零地址的時候只需要檢查一個即可。

      部署合約

      此處部署了新交易對的合約,使用了內(nèi)聯(lián)assemblycreate2是創(chuàng)建合約的方法,它有一個特性就是創(chuàng)建得到的地址可預測:

      address = keccak256(
          0xff,                    // 固定前綴
          deployer,                // 部署者地址(Factory)
          salt,                    // 鹽值
          keccak256(bytecode)      // 字節(jié)碼哈希
      )
      

      這里的salt是用交易對中兩個token的地址生成的,這也就意味著對于任意一對token,最終生成的合約地址是唯一且可預測的,即使合約沒部署也可以通過計算提前知道合約地址。

      初始化

      部署好合約后,調(diào)用了initialize()對合約進行了初始化,并在map里登記了交易對互相之間的映射關(guān)系,然后發(fā)出一條event,標志著交易對創(chuàng)建完成。

      為什么使用initialize調(diào)用進行初始化,而不是在create創(chuàng)建合約的時候通過構(gòu)造函數(shù)初始化呢?

      這是因為如果定義了構(gòu)造函數(shù),那在create的時候傳入的字節(jié)碼里就需要帶上參數(shù)類型并且傳入實參,導致最終得到的hash都不相同。

      特別是外部合約或者其他代碼中計算pair address時,只需要傳入一個固定的常量creationCodeHash即可(直接由uniswap分享出來),而不需要試圖去獲取uniswap的creationCode(得不到)。

      Pair

      Pair合約是uniswap core代碼里面最復雜的部分,負責交易對的相關(guān)內(nèi)容。

      首先從變量定義開始:

      uint public constant MINIMUM_LIQUIDITY = 10**3;
      bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
      

      兩個常量MINIMUM_LIQUIDITYSELECTOR

      其中MINIMUM_LIQUIDITY是對最小流動性的要求,在初次添加流動性的時候會有MINIMUM_LIQUIDITY數(shù)量的LP token被永久鎖定,即使所有的LP都贖回,也保證了池子不會被抽干,LP計算公式永遠有效。

      SELECTOR的預定義是solidity中節(jié)約gas的方法,提前計算selector的字節(jié)碼在合約編譯的時候?qū)懭?/strong>,后續(xù)調(diào)用的時候就無需花費gas重復計算。

      uint112 private reserve0;           // uses single storage slot, accessible via getReserves
      uint112 private reserve1;           // uses single storage slot, accessible via getReserves
      uint32  private blockTimestampLast; // uses single storage slot, accessible via getReserves
      

      reserve是流動性池中代幣的存量,但是和單純的balance不同,因為合約是支持接收轉(zhuǎn)賬的,所以balance的數(shù)量可能因為其他的行為而發(fā)生改變,但reserve的值是統(tǒng)計所有符合Pair邏輯的行為之后得到的流動性池中合法的代幣存量。

      所以reserve并不是一個實時量,而是需要依賴更新操作,因此還需要一個時間變量blockTimestampLast來記錄上次的更新時間。

      在數(shù)據(jù)類型的設(shè)計上,reserve用了uint112而不是uint256之類常見的int長度,這是因為blockTimestampLast需要32位存儲,對于一個uint256來說,還剩下224位,正好分給兩個reserve,112位已經(jīng)能夠滿足單個代幣的供應量。

      這種設(shè)計可以將三個變量放在一個slot中,節(jié)約存儲空間,減少gas的使用。

      在solidity中支持任意8的倍數(shù)的int類型,如uint16uint32都是可以的

      address public factory;
      address public token0;
      address public token1;
      

      定義了最基本的三個元素:

      • factory,創(chuàng)建工廠的地址,避免非法調(diào)用
      • token0token1交易對的兩側(cè)
      uint public price0CumulativeLast;
      uint public price1CumulativeLast;
      uint public kLast;
      

      priceCumulativeLast代表了代幣價格的累積值,用于計算代幣的時間加權(quán)平均價格(TWAP),可以提供給外部作為預言機使用。

      kLast是上次k值(x與y的乘積常量)變動時存儲的值,使用場景在協(xié)議手續(xù)費的計算中。

      event Mint(address indexed sender, uint amount0, uint amount1);
      event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
      event Swap(
          address indexed sender,
          uint amount0In,
          uint amount1In,
          uint amount0Out,
          uint amount1Out,
          address indexed to
      );
      event Sync(uint112 reserve0, uint112 reserve1);
      
      

      Pair里面有四個事件,分別代表著LP的添加和減少,代幣的swap,還有流動性池數(shù)量的更新。

      mint

      function mint(address to) external lock returns (uint liquidity) {
          (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
          uint balance0 = IERC20(token0).balanceOf(address(this));
          uint balance1 = IERC20(token1).balanceOf(address(this));
          uint amount0 = balance0.sub(_reserve0);
          uint amount1 = balance1.sub(_reserve1);
      
          bool feeOn = _mintFee(_reserve0, _reserve1);
          uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
          if (_totalSupply == 0) {
              liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
             _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
          } else {
              liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
          }
          require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
          _mint(to, liquidity);
      
          _update(balance0, balance1, _reserve0, _reserve1);
          if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
          emit Mint(msg.sender, amount0, amount1);
      }
      
      

      mint是Pair里面的關(guān)鍵方法之一,調(diào)用時間在合約轉(zhuǎn)入流動性池資產(chǎn)之后,根據(jù)轉(zhuǎn)入的數(shù)量會給對應的用戶mint出LP token。

      首先通過reservebalance的差值計算出amount,也就是用戶轉(zhuǎn)入作為lp的代幣數(shù)量。

      feeOn是uniswap中手續(xù)費的設(shè)計,不影響主流程,放到最后再講。

      _mintFee之后,讀取了當前l(fā)p token的總供應量,這里有兩個注意的點:

      • 順序問題,_mintFee中會影響supply的數(shù)量,所以必須在其之后讀取
      • gas優(yōu)化問題,在方法如果要讀取合約的成員變量,應當使用一個臨時變量去做記錄,方法內(nèi)變量的使用gas要低于讀取合約的變量。

      根據(jù)totalSupply分成兩種邏輯:

      • 初次添加流動性,計算公式為$\sqrt {x*y}$,額外還需要減去MINIMUM_LIQUIDITY,這也是上面提到過的鎖定流動性,然后這部分流動性會被打到零地址去。
      • 正常有池子的情況下流動性的計算公式是$\frac{totalSupply}{reserve}*amount$,也就是保證totalSupplyreserve比值不變的情況下增加amount的數(shù)量,
        • 如果在添加單個代幣流動性的情況下,直接這么計算就可以,用戶得到的LP token價值與當前流動性池子內(nèi)的LP token價值是相等的。
        • 如果是雙代幣添加,那么就要取兩個值中的較小值。
      function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
          require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
          uint32 blockTimestamp = uint32(block.timestamp % 2**32);
          uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
          if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
              // * never overflows, and + overflow is desired
              price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
              price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
          }
          reserve0 = uint112(balance0);
          reserve1 = uint112(balance1);
          blockTimestampLast = blockTimestamp;
          emit Sync(reserve0, reserve1);
      }
      

      block.timestamp的類型是uint256,但這里只保留了低32位,這樣設(shè)計是因為uniswap中用的是時間差值而非時間本身,即 uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired,因為是無符號整數(shù),只要兩次時間的差值不超過uint32的表示范圍,那么即使是溢出取模的情況下依然可以保證差值是正確的。

      在計算Cumulative的時候要注意,這里出現(xiàn)了UQ112x112UQ112x112是uniswap自己實現(xiàn)的庫,作用是用一個uint224表示定點數(shù),整數(shù)和小數(shù)部分各分配112位,這是因為solidity沒有原生的小數(shù)類型,而此處又涉及到了除法。encode的作用是將uint值左移112位,右邊的112位用于表示小數(shù),uqdiv是UQ112x112中自定義的除法,計算的結(jié)果依然是UQ112x112類型。

      回到方法本身,timeElapsed * (reserve1/reserve0)表示price(reserve1/reserve0)持續(xù)了timeElapsed這么久,稱為時間加權(quán)的價格累計。使用的時候?qū)蓚€時間點的累積值相減再除以間隔時間,就可以得到這段時間內(nèi)的時間加權(quán)平均價格。

      最后更新合約變量,輸出事件,_update結(jié)束。

      burn

      function burn(address to) external lock returns (uint amount0, uint amount1) {
          (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
          address _token0 = token0;                                // gas savings
          address _token1 = token1;                                // gas savings
          uint balance0 = IERC20(_token0).balanceOf(address(this));
          uint balance1 = IERC20(_token1).balanceOf(address(this));
          uint liquidity = balanceOf[address(this)];
      
          bool feeOn = _mintFee(_reserve0, _reserve1);
          uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
          amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
          amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
          require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
          _burn(address(this), liquidity);
          _safeTransfer(_token0, to, amount0);
          _safeTransfer(_token1, to, amount1);
          balance0 = IERC20(_token0).balanceOf(address(this));
          balance1 = IERC20(_token1).balanceOf(address(this));
      
          _update(balance0, balance1, _reserve0, _reserve1);
          if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
          emit Burn(msg.sender, amount0, amount1, to);
      }
      
      

      在mint中,可以看到計算提取出代幣數(shù)量amount的時候,基數(shù)用的是balance/totalSupply而不是reserve,這是因為reserve的更新有滯后性,并且uniswap認為Pair中的所有資產(chǎn)都是屬于LP的,即使是不通過合約方法存入的部分,都可以根據(jù)lp token獲得分成。

      其他部分與mint基本類似,就不重復說明了。

      swap

      function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
          require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
          (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
          require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
      
          uint balance0;
          uint balance1;
          { // scope for _token{0,1}, avoids stack too deep errors
          address _token0 = token0;
          address _token1 = token1;
          require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
          if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
          if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
          if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
          balance0 = IERC20(_token0).balanceOf(address(this));
          balance1 = IERC20(_token1).balanceOf(address(this));
          }
          uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
          uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
          require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
          { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
          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');
          }
      
          _update(balance0, balance1, _reserve0, _reserve1);
          emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
      }
      
      

      可以看到swap方法的參數(shù)中只有amountOut的值,而沒有amountIn,說明在swap中amountIn是依賴于amountOut計算出來的,并且在實現(xiàn)中是先轉(zhuǎn)出out資產(chǎn),再去判斷in是否滿足,這種設(shè)計有以下的原因:

      • 支持閃電貸功能,因為閃電貸的功能依賴于轉(zhuǎn)出資產(chǎn)套利后再補回,用戶先要得到out資產(chǎn)才可以
      • 保證balance的正確性,因為swap中的流動性池需要滿足常數(shù)k條件,必須用新的balance參與計算得到另一個token的balance

      進入具體方法里面,首先是對數(shù)值有效性的判斷,然后就直接將amountOut通過_safeTransfer轉(zhuǎn)給了to地址,這也是我們前面提到的先outin

      轉(zhuǎn)賬之后做了一個data長度的判斷,此處就是對閃電貸支持的實現(xiàn),借貸的對象需要實現(xiàn)IUniswapV2Callee中的uniswapV2Call方法供uniswap調(diào)用,并在其中實現(xiàn)套利-還款的邏輯,保證最終的amountIn與out的資產(chǎn)相匹配。

      amountIn的計算公式是:balance - (_reserve - amountOut),雖然通常在dex中都是單邊輸入單邊輸出,但swap的底層實現(xiàn)其實是支持雙輸出和雙輸入的,只要保證最后的余額滿足常數(shù)k的約束即可。

      uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));是uniswap計算手續(xù)費的公式,首先mul(1000)是為了用整數(shù)的精度計算,其實等價于為balance-(amountIn*0.003),即收取amountIn 0.3%的手續(xù)費。 而require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');規(guī)定了收取手續(xù)費之后的balance的常數(shù)k需要不小于流動性池現(xiàn)有存儲的常數(shù)k。那么再加上手續(xù)費,流動性池的k值其實是上升的,意外著LP能夠兌換的資產(chǎn)也變多了,所有的LP都能夠通過手續(xù)費受益。

      對于uniswap來說,用戶發(fā)起swap后會馬上用戶轉(zhuǎn)出out資產(chǎn),但在最后結(jié)算時,合約中必須新增滿足條件的in資產(chǎn),即扣減手續(xù)費之后流動性池的常數(shù)k值不能減少,至于中間發(fā)生了什么,合約并不關(guān)心。

      協(xié)議費

      uniswap中協(xié)議費是可以手動控制開啟關(guān)閉的,協(xié)議費的來源就是手續(xù)費,開啟feeOn的情況下,uniswap可以從手續(xù)費中得到協(xié)議分成。

      function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
          address feeTo = IUniswapV2Factory(factory).feeTo();
          feeOn = feeTo != address(0);
          uint _kLast = kLast; // gas savings
          if (feeOn) {
              if (_kLast != 0) {
                  uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
                  uint rootKLast = Math.sqrt(_kLast);
                  if (rootK > rootKLast) {
                      uint numerator = totalSupply.mul(rootK.sub(rootKLast));
                      uint denominator = rootK.mul(5).add(rootKLast);
                      uint liquidity = numerator / denominator;
                      if (liquidity > 0) _mint(feeTo, liquidity);
                  }
              }
          } else if (_kLast != 0) {
              kLast = 0;
          }
      }
      

      協(xié)議費的公式可以寫作:協(xié)議費LP代幣 = S × (rootK - rootKLast) / (5 × rootK + rootKLast),其中S為totalSupply,這個公式是由[S × (rootK - rootKLast)/rootKLast] × (1/6)得到的,也就是要分成從上次kLast到這次k中間新增LP數(shù)量的1/6。

      kLast只有在swap的過程中才會變更,并且保證了k緩慢增長的時候,協(xié)議能夠根據(jù)這些額外增長的k去mint出LP token,再根據(jù)LP token來獲取分成受益。

      如果池子里:

      • 純粹的 mint/burn 操作(不涉及 swap)
      • 沒有交易活動的靜態(tài)池子
      • 剛剛收取過協(xié)議費的池子(此時 kLast 會被更新)

      那么也就無法計算出協(xié)議費,因為k未改變。

      posted @ 2025-08-25 16:49  Felix07  閱讀(143)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 一区二区在线观看成人午夜| 三级三级三级A级全黄| 国产成人啪精品视频免费软件| 成人免费无码大片a毛片| 蜜臀av午夜精品福利| 日韩精品无码不卡无码| 欧美粗大| 国产睡熟迷奷系列网站| 泾源县| 日韩永久永久永久黄色大片| 日日碰狠狠添天天爽五月婷| 五月婷婷久久中文字幕| 中文字幕亚洲精品第一页| 国产精品久久蜜臀av| 久久久久久综合网天天| 亚洲国产精品久久久久秋霞| 欧美日韩另类国产| 国产精品综合色区av| 亚洲人精品午夜射精日韩| 国产伦精品一区二区三区| 日本一区二区精品色超碰| 国产在线播放专区av| 欧美极品色午夜在线视频| 九九热视频在线观看精品| 中文字幕日韩精品无码内射| 亚洲春色在线视频| 精品一区二区三区在线观看l| 她也色tayese在线视频| 日韩av综合中文字幕| 亚洲国产在一区二区三区| 日韩大尺度一区二区三区| 成人午夜电影福利免费| 国产超碰人人做人人爰| 青青青爽在线视频观看| 泸水县| 免费国产高清在线精品一区| 高中女无套中出17p| 国产亚洲欧美日韩俺去了| 亚洲爆乳WWW无码专区| 桃花岛亚洲成在人线AV| 日韩一区二区三区在线观院|