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

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

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

      UniswapV2Periphery 源碼學習

      Periphery是uniswap的外圍合約,將core合約封裝起來提供給外部調用,比如我們在網頁操作Swap時,請求的就是Periphery的合約。

      Periphery里面寫了Migrator和Router兩個合約,其中Migrator是遷移合約,將流動性從Uniswap的V1版本遷移到V2版本,不涉及swap的功能,這里就不寫了。

      Router合約

          using SafeMath for uint;
      
          address public immutable override factory;
          address public immutable override WETH;
      
          modifier ensure(uint deadline) {
              require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
              _;
          }
      
          constructor(address _factory, address _WETH) public {
              factory = _factory;
              WETH = _WETH;
          }
      
          receive() external payable {
              assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
          }
      

      從基礎部分開始看起,router合約中記錄了factoryWETH地址,其中factory用于獲取pair和創建新的pair合約,而特別記錄下WETH的地址是為了支持以太坊鏈的主網幣ETH

      Uniswap中的代幣操作都是基于ERC20類型,但是ETH本身既不是ERC20,也沒有合約地址,因此為了ETH也能參與swap,需要先將ETH轉換成WETH,再進行后續的操作。Uniswap為了減少用戶手動轉換的麻煩,會在有ETH參與的交易中自動執行ETHWETH的相互轉換,因此需要記錄下WETH的合約地址。

      receive方法中限制了只允許接收來自WETH合約的ETH,即調用withdraw方法取出ETH,除此之外不可直接向合約中轉入ETH。

      addLiquidity

      addLiquidity是向合約添加流動性的方法,其主要邏輯在_addLiquidity中,根據用戶提供的token數量,再根據流動性池中已有的token數量,計算出實際參與添加流動性的token數量,返回兩個uint值:

      function _addLiquidity(
          address tokenA,
          address tokenB,
          uint amountADesired,
          uint amountBDesired,
          uint amountAMin,
          uint amountBMin
      ) internal virtual returns (uint amountA, uint amountB) {
          // create the pair if it doesn't exist yet
          if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
              IUniswapV2Factory(factory).createPair(tokenA, tokenB);
          }
          (uint reserveA, uint reserveB) = UniswapV2Library.getReserves(factory, tokenA, tokenB);
          if (reserveA == 0 && reserveB == 0) {
              (amountA, amountB) = (amountADesired, amountBDesired);
          } else {
              uint amountBOptimal = UniswapV2Library.quote(amountADesired, reserveA, reserveB);
              if (amountBOptimal <= amountBDesired) {
                  require(amountBOptimal >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
                  (amountA, amountB) = (amountADesired, amountBOptimal);
              } else {
                  uint amountAOptimal = UniswapV2Library.quote(amountBDesired, reserveB, reserveA);
                  assert(amountAOptimal <= amountADesired);
                  require(amountAOptimal >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
                  (amountA, amountB) = (amountAOptimal, amountBDesired);
              }
          }
      }
      

      第一步判斷交易對是否存在,如果不存在那么調用facotry創建一個新的交易對。

      如果此時流動性池為空,那么用戶提供的數量就是最后實際添加到池子中的數量,無需進一步計算;但如果池子非空,就需要通過UniswapV2Library中的quote方法去計算合理的數量。

      quote方法如下:

      function quote(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint amountB) {
          require(amountA > 0, 'UniswapV2Library: INSUFFICIENT_AMOUNT');
          require(reserveA > 0 && reserveB > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
          amountB = amountA.mul(reserveB) / reserveA;
      }
      

      邏輯很簡單,就是根據A和B當前數量的比值,計算新增數量的A需要匹配多少數量的B,保證最終池子內A與B的比值不變。

      回到_addLiquidity的邏輯,先根據A傳入的數量去計算出需要多少相匹配的B,如果傳入的B數量滿足,那么就以amountADesired, amountBOptimal作為最后添加到流動性池子的數量;如果不滿足,說明B相對池子的數量較少,那么就以B的數量為基準,反過來去計算所需要A的數量。在計算中,還需要滿足amountMin的限制。

      了解了主要邏輯之后,再回歸到addLiquidity方法本身就很簡單了:

      function addLiquidity(
          address tokenA,
          address tokenB,
          uint amountADesired,
          uint amountBDesired,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
      ) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity) {
          (amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
          address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
          TransferHelper.safeTransferFrom(tokenA, msg.sender, pair, amountA);
          TransferHelper.safeTransferFrom(tokenB, msg.sender, pair, amountB);
          liquidity = IUniswapV2Pair(pair).mint(to);
      }
      

      pairFor方法就是之前提到過的唯一pair地址生成器,根據factory,tokenA和tokenB的地址就能生成對應的pair地址,無需去factory中查詢。

      safeTransferFrom是uniswap封裝的轉賬方法,因為標準的ERC20實現中tranferFrom要求返回bool,但是實際有許多代幣在實現的時候并沒有遵守這一規則,導致返回內容各不相同,還可能不返回,因此通過底層調用的繞過類型檢查的限制,并且手動根據返回的data元數據進行判斷調用是否成功,保證了對不同token的兼容。

      function safeTransferFrom(
          address token,
          address from,
          address to,
          uint256 value
      ) internal {
          // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
          (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
          require(
              success && (data.length == 0 || abi.decode(data, (bool))),
              'TransferHelper::transferFrom: transferFrom failed'
          );
      }
      

      addLiquidityETH

      addLiquidityETH的使用場景是交易中存在一方為ETH的時候,需要執行前面提到的WETH轉換操作,并且ETH是通過msg.Value的形式傳遞的,所以對于多余的部分,需要手動執行退回。

      function addLiquidityETH(
          address token,
          uint amountTokenDesired,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      ) external virtual override payable ensure(deadline) returns (uint amountToken, uint amountETH, uint liquidity) {
          (amountToken, amountETH) = _addLiquidity(
              token,
              WETH,
              amountTokenDesired,
              msg.value,
              amountTokenMin,
              amountETHMin
          );
          address pair = UniswapV2Library.pairFor(factory, token, WETH);
          TransferHelper.safeTransferFrom(token, msg.sender, pair, amountToken);
          IWETH(WETH).deposit{value: amountETH}();
          assert(IWETH(WETH).transfer(pair, amountETH));
          liquidity = IUniswapV2Pair(pair).mint(to);
          // refund dust eth, if any
          if (msg.value > amountETH) TransferHelper.safeTransferETH(msg.sender, msg.value - amountETH);
      }
      

      removeLiquidity

      removeLiquidity的基本邏輯:

      • 獲取交易對Pair
      • senderLP token發送Pair
      • 調用burn方法,銷毀LP token,將兩種token發回給用戶,并得到tokenAtokenB的數量
      • 保證數量滿足min的要求
      function removeLiquidity(
          address tokenA,
          address tokenB,
          uint liquidity,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
      ) public virtual override ensure(deadline) returns (uint amountA, uint amountB) {
          address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
          IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity); // send liquidity to pair
          (uint amount0, uint amount1) = IUniswapV2Pair(pair).burn(to);
          (address token0,) = UniswapV2Library.sortTokens(tokenA, tokenB);
          (amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
          require(amountA >= amountAMin, 'UniswapV2Router: INSUFFICIENT_A_AMOUNT');
          require(amountB >= amountBMin, 'UniswapV2Router: INSUFFICIENT_B_AMOUNT');
      }
      

      removeLiquidityETH

      removeLiquidityETH同樣是用于ETH參與交易對的場景,可以看到這里直接調用了removeLiquidity,但調用的時候to參數傳的是路由合約的地址address(this),這意味著burn取回流動性之后,代幣會先發送到路由合約上。因此下面的邏輯補上了從路由合約將token和ETH轉回到to地址的過程。

      function removeLiquidityETH(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      ) public virtual override ensure(deadline) returns (uint amountToken, uint amountETH) {
          (amountToken, amountETH) = removeLiquidity(
              token,
              WETH,
              liquidity,
              amountTokenMin,
              amountETHMin,
              address(this),
              deadline
          );
          TransferHelper.safeTransfer(token, to, amountToken);
          IWETH(WETH).withdraw(amountETH);
          TransferHelper.safeTransferETH(to, amountETH);
      }
      

      這么寫是因為:

      • 需要處理WETHETH的轉換,因此必須將WETH先取出,存到路由合約中
      • 復用了removeLiquidity邏輯,簡化代碼

      其他remove

      uniswap中還支持了removeLiquidityWithPermitremoveLiquidityETHSupportingFeeOnTransferTokens這兩種類型,其中WithPermit是基于EIP712實現的鏈下簽名代執行的方法,而SupportingFeeOnTransferTokens則是支持特殊的ERC20token,這種token會在交易的過程中收取手續費或者燃燒,因為不涉及核心邏輯,所以就不深入了。

      swap

      swap有四種類型:

      • swapExactTokensForTokens,拿指定數量的A換B
      • swapTokensForExactTokens,拿A換指定數量的B
      • swapExactETHForTokens,拿指定數量的ETH換token
      • swapTokensForExactETH,拿ETH換指定數量的token

      可以看到,關鍵的區別在于先確定輸入還是先確定輸出,以及是否有ETH的參與。

      swapExactTokensForTokens為例:

      function swapExactTokensForTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external virtual override ensure(deadline) returns (uint[] memory amounts) {
          amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
          require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
          TransferHelper.safeTransferFrom(
              path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
          );
          _swap(amounts, path, to);
      }
      

      path是token轉換的路徑,因為對于用戶想要提供A換取B的場景, 可能沒有現成的A-B池子,那么就需要一條路徑,先將A換成C,再從C換成B,最典型的C就是WETH,因為絕大部分的代幣都會優先提供和WETH組成的交易對,那么只要通過WETH,基本上就可以實現任意兩種代幣的兌換。

      根據path可以得到amounts,即轉換路徑上每種代幣應有的數量,因為這里是已知輸入的方法,所以用到了getAmountsOut方法:

      function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
          require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
          amounts = new uint[](path.length);
          amounts[0] = amountIn;
          for (uint i; i < path.length - 1; i++) {
              (uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
              amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
          }
      }
      
      function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
            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;
        }
      

      getAmountsOut即輪詢path中的代幣組合,模擬tokenswapgetAmountOut是對于已知reservepair,提供amountIn得到amountOut

      getAmountOut中是以下數學邏輯的實現:

      交換前:x × y = k
      交換后:(x + Δx) × (y - Δy) = k

      因為k是常數,所以:
      x × y = (x + Δx) × (y - Δy)

      展開:
      x × y = x × y - x × Δy + Δx × y - Δx × Δy

      簡化:
      0 = -x × Δy + Δx × y - Δx × Δy
      x × Δy = Δx × y - Δx × Δy
      x × Δy = Δx × (y - Δy)

      求解Δy:
      Δy = (Δx × y) / (x + Δx)

      也就是amountOut = (amountIn × reserveOut) / (reserveIn + amountIn)

      因為uniswap中會收取0.3%的手續費,所以實際的amountIn是 amountIn *997/100,為了避免浮點數運算,分子分母都乘以1000,最終得到amountOut = (amountIn × 997 × reserveOut) / (reserveIn × 1000 + amountIn × 997)

      計算出amounts后,將input token發送到即path[0]path[1]組成的流動性池,調用_swap進行鏈式的交換,直到最終得到output

      function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
          for (uint i; i < path.length - 1; i++) {
              (address input, address output) = (path[i], path[i + 1]);
              (address token0,) = UniswapV2Library.sortTokens(input, output);
              uint amountOut = amounts[i + 1];
              (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
              address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
              IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                  amount0Out, amount1Out, to, new bytes(0)
              );
          }
      }
      

      _swap主要做了參數的處理工作,遍歷pathamounts得到inputoutputamount0Outamount1Out等參數,傳入Pair合約的swap方法中進行實際的swap工作。

      注意的幾個點:

      • amountOut等于amounts[i+1]且需要分配給非inputtoken作為amount
      • swap的時候,path[i]path[i+1]的輸出token要發給path[i+1]path[i+2]的pair池子,所以當i=path.length-2的時候,i+1為最后一個token,此時發送的對象為_to,也就是輸出給指定的用戶地址而非Pair合約。

      swapExactETHForTokens

      swapExactETHForTokens的邏輯基本類似,但是所有用到ETH的地方都必須做WETH的轉換,比如一開始就要求 path[0]必須為WETH。然后將ETH轉換為WETH后發給第一個交易對,開始swap的流程。

      function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
          external
          virtual
          override
          payable
          ensure(deadline)
          returns (uint[] memory amounts)
      {
          require(path[0] == WETH, 'UniswapV2Router: INVALID_PATH');
          amounts = UniswapV2Library.getAmountsOut(factory, msg.value, path);
          require(amounts[amounts.length - 1] >= amountOutMin, 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT');
          IWETH(WETH).deposit{value: amounts[0]}();
          assert(IWETH(WETH).transfer(UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]));
          _swap(amounts, path, to);
      }
      
      

      總結

      在Router中主要實現的是對于參數的處理,無論是流動性的變更還是swap,在用戶提供了tokenamount之后,路由合約會進行相應的計算,得到滿足條件的amount參與到swap流程中,保證了傳遞給swap方法的參數合法性。同時也要負責多鏈路swap的有序進行,實現不同流動性池之間的傳遞。

      posted @ 2025-09-01 13:32  Felix07  閱讀(97)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 国产女人高潮视频在线观看| 国产一级r片内射免费视频| 人妻精品动漫H无码中字| 香港经典a毛片免费观看播放| 国内揄拍国内精品少妇国语| 中文字幕精品人妻丝袜| 国产精品爽爽久久久久久| 亚洲天堂激情av在线| 国产精品99中文字幕| 云梦县| 九九热在线观看视频精品| 午夜大尺度福利视频一区| 欧美日本一区二区视频在线观看| 亚洲aⅴ无码专区在线观看春色| 免费无码又爽又刺激高潮虎虎视频| 亚洲人成线无码7777| 丰满人妻一区二区三区高清精品 | 无码加勒比一区二区三区四区| 美女自卫慰黄网站| 粗壮挺进人妻水蜜桃成熟| 人人爽亚洲aⅴ人人爽av人人片| 亚洲乱码一卡二卡卡3卡4卡| 台南县| 精品视频福利| 国产精品免费看久久久| 国产精品剧情亚洲二区| 色呦呦九九七七国产精品| 久久综合国产色美利坚| 亚洲精品福利一区二区三区蜜桃 | 日本一区二区三区在线播放| 男女男免费视频网站国产| 久久中文字幕日韩无码视频| 精品一区二区三区蜜桃麻豆| 久久精品av国产一区二区| 18禁免费无码无遮挡网站| 国产精品午夜av福利| 景洪市| 午夜免费视频国产在线 | 滦平县| 国产女人看国产在线女人| 国产精品熟女孕妇一区二区|