主頁 > 前端設計 > UniswapV2周邊合約學習(二)-- UniswapV2Router02.sol(上)

UniswapV2周邊合約學習(二)-- UniswapV2Router02.sol(上)

2020-10-21 03:15:16 前端設計

記得朋友圈看到過一句話,如果Defi是以太坊的皇冠,那么Uniswap就是這頂皇冠中的明珠,Uniswap目前已經是V2版本,相對V1,它的功能更加全面優化,然而其合約原始碼卻并不復雜,本文為個人學習UniswapV2原始碼的系列記錄文章,

一、Router合約介紹

UniswapV2的周邊合約主要作用是作為用戶和核心合約之間的橋梁,也就是用戶 => 周邊合約 => 核心合約,UniswapV2周邊合約主要包含介面定義,工具庫和核心實作這三部分,在上一篇文章里已經學習了它的工具庫函式,這次我們主要學習其核心實作,

UniswapV2周邊合約的核心實作包含UniswapV2Router01.solUniswapV2Router02.sol,這里我們把它簡稱為Router1Router2,查看它們實作的介面我們可以看到,Router2僅在Router1上多了幾個介面,那為什么會有兩個路由合約呢,我們到呼叫哪個呢?查看其官方檔案我們可以得到:

Because routers are stateless and do not hold token balances, they can be replaced safely and trustlessly, if necessary. This may happen if more efficient smart contract patterns are discovered, or if additional functionality is desired. For this reason, routers have release numbers, starting at 01. This is currently recommended release, 02.

上面那段話的大致意思就是因為Router合約是無狀態的并且不擁有任何代幣,因此必要的時候它們可以安全升級,當發現更高效的合約模式或者添加更多的功能時就可能升級它,因為這個原因,Router合約具有版本號,從01開始,當前推薦的版本是02

這段話解釋了為什么會有兩個Router,那么它們的區別是什么呢?還是來看官方檔案:

UniswapV2Router01 should not be used any longer, because of the discovery of a low severity bug and the fact that some methods do not work with tokens that take fees on transfer. The current recommendation is to use UniswapV2Router02.

這段話是講因為在Router1中發現了一個低風險的bug,并且有些方法不支持使用轉移的代幣支付手續費,所以不再使用Router1,推薦使用Router2

因此本文也是學習的UniswapV2Router02.sol,它的前半部分主要是流動性供給相關的函式(功能),后半部分主要是交易對資產交換相關的函式(功能),由于篇幅較長,因此該合約學習計劃分為上、下兩個部分來學習,內容分別為流動性供給函式和資產交換函式,這次先學習流動性供給部分,

建議對UniswapV2不熟的讀者在開始學習之前閱讀我的另一篇文章:UniswapV2介紹 來對UniswapV2的整體機制有個大致了解;當然也建議閱讀前面的系列文章,特別是核心合約學習部分,這樣更有助于理解原始碼,

UniswapV2周邊合約在Github上的地址為: uniswap-v2-periphery

二、原始碼中的公共部分

UniswapV2Router02.sol原始碼的公共部分從第一行開始,到回呼函式receive結束,主要是匯入檔案和公共變數定義、函式修飾符及構造器等,

  • 第一行,指定Solidity版本

  • 第2-3行,匯入Node.js依賴庫,注意匯入的檔案也是.sol結尾,第一個為核心合約factory的介面,第二個為TransferHelper庫,這個庫在我的上一篇文章周邊合約工具庫學習時有簡單提及,

  • 4-8行,匯入專案內其它介面或者庫,分別為本合約要實作的介面,自定義的工具庫(在周邊合約學習一中有介紹),SafeMath標準ERC20介面和WETH介面,

  • contract *UniswapV2Router02* is IUniswapV2Router02 { 合約定義,本合約實作了IUniswapV2Router02介面,

  • using SafeMath for uint;很常見,在uint上使用SafeMath,防止上下溢位,

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

    這兩行代碼使用兩個狀態變數分別記錄了factory合約的地址WETH合約的地址,這里有兩個關鍵詞immutableoverride需要深入學習一下,

    • immutable,不可變的,類似別的語言的final變數,也就是它初始化后值就無法再改變了,它和constant(常量)類似,但又有些不同,主要區別在于:常量在編譯時就是確定值,而immutable狀態變數除了在定義的時候初始化外,還可以在構造器中初始化(合約創建的時候),并且在構造器中只能初始化,是讀取不了它們的值的,并不是所有資料型別都可以為immutable變數或者常量的型別,當前只支持值型別和字串型別(string),

    • override這個很常見,通常用于函式定義中,代表它重寫了一個父函式,例如也可以用于函式修飾符來代表它被重寫,不過應用于狀態變數卻稍有不同,

      Public state variables can override external functions if the parameter and return types of the function matches the getter function of the variable:

      這句話的意思是:如果external函式的引數和回傳值同公共狀態變數的getter函式相符的話,這個公共狀態變數可以重寫該函式,但是狀態變數本身卻不能被重寫,我們來找一下它到底重寫了哪個函式,在它實作的介面IUniswapV2Router02中,有這么一個函式定義:

      function factory() external pure returns (address);,可見factory公共狀態變數重寫了其介面的external同名函式,

      這里有人可能會問,Router2介面定義中不是沒有這個函式嗎?因為Router2介面繼承了Router1介面,Router1介面定義了該函式,Router2介面就自動擁有該函式,

  • 接下來是個ensure構造器修飾符,比較簡單,就是判定當前區塊(創建)時間不能超過最晚交易時間,代碼為:

    modifier ensure(uint deadline) {
        require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
        _;
    }
    
  • 接下來是構造器,也很簡單,將上面兩個immutable狀態變數初始化,

    constructor(address _factory, address _WETH) public {
        factory = _factory;
        WETH = _WETH;
    }
    
  • 接下來是一個接收ETH的函式receive,從Solidity 0.6.0起,沒有匿名回呼函式了,它拆分成兩個,一個專門用于接收ETH,就是這個receive函式,另外一個在找不到匹配的函式時呼叫,叫fallback函式,該receive函式限定只能從WETH合約直接接收ETH,也就是在WETH提取為ETH時,注意仍然有可以有別的方式來向此合約直接發送以太幣,例如設定為礦工地址等,這里不展開闡述,

    receive() external payable {
        assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
    }
    

三、原始碼中的流動性供給部分

  • _addLiquidity函式,看名字為增加流動性,為一個internal函式,提供給多個外部介面呼叫,它主要功能是計算擬向交易對合約注入的代幣數量,函式代碼如下:

    // **** ADD LIQUIDITY ****
    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);
            }
        }
    }
    

    該函式以下劃線開頭,根據約定一般它為一個內部函式,六個輸入引數分別為交易對中兩種代幣的地址,計劃注入的兩種代幣數量和注入代幣的最小值(否則重置),回傳值為優化過的實際注入的代幣數量,

    • 函式的前三行,注釋說的很清楚,如果交易對不存在(獲取的地址為零值),則創建之,

    • 函式的第四行獲取交易對資產池中兩種代幣reserve數量,當然如果是剛創建的,就都是0,

    • 第五行到結束是一個if - else陳述句,如果是剛創建的交易對,則擬注入的代幣全部轉化為流動性,初始流動性計算公式及初始流動性燃燒見我的核心合約學習三那篇文章,如果交易對已經存在,由于注入的兩種代幣的比例和交易對中資產池中的代幣比例可能不同,再用一個if - else陳述句來選擇以哪種代幣作為標準計算實際注入數量,(如果比例不同,總會存在一種代幣多一種代幣少,肯定以代幣少的計算實際注入數量),

      這里可以這樣理解,假定A/B交易對,然后注入了一定數量的A和B,根據交易對當前的比例,如果以A計算B,B不夠,此時肯定不行;只能反過來,以B計算A,這樣A就會有多余的,此時才能進行實際注入(這樣注入的A和B數量都不會超過擬注入數量),

    • 那為什么要按交易對的比例來注入兩種代幣呢?在核心合約學習三那篇文章里有提及,流動性的增加數量是分別根據注入的兩種代幣的數量進行計算,然后取最小值,如果不按比例交易對比例來充,就會有一個較大值和一個較小值,取最小值流行性提供者就會有損失,如果按比例充,則兩種代幣計算的結果一樣的,也就是理想值,不會有損失,

    • 該函式也涉及到了部分UniswapV2Library庫函式的呼叫,可以看上一篇文章周邊合約工具庫學習,

  • addLiquidity函式,學習了前面的_addLiquidity函式,這個就比較好理解了,它是一個external函式,也就是用戶呼叫的介面,函式引數和_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);
    }
    

    這里deadline從UniswapV1就開始存在了,主要是保護用戶,不讓交易過了很久才執行,超過用戶預期,函式回傳值是實際注入的兩種代幣數量和得到的流動性代幣數量,

    • 函式的第一行是呼叫_addLiquidity函式計算需要向交易對合約轉移(注入)的實際代幣數量,

    • 函式的第二行是獲取交易對地址(注意,如果交易對不存在,在對_addLiquidity呼叫時會創建),注意,它和_addLiquidity函式獲取交易對地址略有不同,一個是呼叫factory合約的介面得到(這里不能使用根據salt創建合約的方式計算得到,因為不管合約是否存在,總能得到該地址);另一個是根據salt創建合約的方式計算得到,雖然兩者用起來都沒有問題,個人猜想本函式使用salt方式計算是因為呼叫的庫函式是pure的,不讀取狀態變數,并且為內部呼叫,能節省gas;而呼叫factory合約介面是個外部EVM呼叫,有額外的開銷,個人猜想,未必正確,

    • 第三行和第四行是將實際注入的代幣轉移至交易對,

    • 第五行是呼叫交易對合約的mint函式來給接收者增發流動性,

    對于這個合約介面(外部函式),Uniswap檔案也提到了三點注意事項:

    1. 為了覆寫所有場景,呼叫者需要給該Router合約一定額度的兩種代幣授權,因為注入的資產為ERC20代幣,第三方合約如果不得到授權(或者授權額度不夠),就無法轉移你的代幣到交易對合約中去,
    2. 總是按理想的比例注入代幣(因為計算比例和注入在一個交易內進行),具體取決于交易執行時的價格,這一點在介紹_addLiquidity函式時已經講了,
    3. 如果交易對不存在,則會自動創建,擬注入的代幣數量就是真正注入的代幣數量,
  • addLiquidityETH函式,和addLiquidity函式類似,不過這里有一種初始注入資產為ETH,因為UniswapV2交易對都是ERC20交易對,所以注入ETH會先自動轉換為等額WETH(一種ERC20代幣,通過智能合約自由兌換,比例1:1),這樣就滿足了ERC20交易對的要求,因此真實交易對為WETH/ERC20交易對,

    本函式的引數和addLiquidity函式的引數相比,只是將其中一種代幣換成了ETH,注意這里沒有擬注入的amountETHDesired,因為隨本函式發送的ETH數量就是擬注入的數量,所以該函式必須是payable的,這樣才可以接收以太幣,函式代碼為:

    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);
    }
    
    • 函式的第一行仍舊是呼叫_addLiquidity函式來計算優化后的注入代幣值,正如前面分析的那樣,它使用WETH地址代替另一種代幣地址,使用msg.value來代替擬注入的另一種代幣(因為WETH與ETH是等額兌換)數量,當然,如果WETH/TOKEN交易對不存在,則先創建之,

    • 函式的第二行是獲取交易對地址,注意它獲取的方式仍然是計算得來,

    • 第三行是將其中一種代幣token轉移到交易對中(轉移的數量為由第一行計算得到)

    • 第四行是將ETH兌換成WETH,它呼叫了WETH合約的兌換介面,這些介面在IWETH.sol中定義,兌換的數量也在第一行中計算得到,當然,如果ETH數量不夠,則會重置整個交易,

    • 第五行將剛剛兌換的WETH轉移至交易對合約,注意它直接呼叫的WETH合約,因此不是授權交易,不需要授權,另外由于WETH合約開源,可以看到該合約代碼中轉移資產成功后會回傳一個true,所以使用了assert函式進行驗證,

    • 第六行是呼叫交易對合約的mint方法來給接收者增發流動性,

    • 最后一行是如果呼叫進隨本函式發送的ETH數量msg.value有多余的(大于amountETH,也就是兌換成WETH的數量),那么多余的ETH將退還給呼叫者,

  • removeLiquidity函式,移除(燃燒)流動性(代幣),從而提取交易對中注入的兩種代幣,該函式的7個引數分別為兩種代幣地址,燃燒的流動性數量,提取的最小代幣數量(保護用戶),接收者地址和最遲交易時間,它的回傳引數是提取的兩種代幣數量,該函式是virtual的,可被子合約重寫,正如前面所講,本合約是無狀態的,是可以升級和替代的,因此本合約所有的函式都是virtual的,方便新合約重寫它,下面是該函式的代碼片斷:

    // **** REMOVE LIQUIDITY ****
    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');
    }
    
    • 函式的第一行計算兩種代幣的交易對地址,注意它是計算得來,而不是從factory合約查詢得來,所以就算該交易對不存在,得到的地址也不是零地址,

    • 函式的第二行呼叫交易對合約的授權交易函式,將要燃燒的流動性轉回交易對合約,如果該交易對不存在,則第一行代碼計算出來的合約地址的代碼長度就為0,呼叫其transferFrom函式就會報錯重置整個交易,所以這里不用擔心交易對不存在的情況,

    • 函式的第三行呼叫交易對的burn函式,燃燒掉剛轉過去的流動性代幣,提取相應的兩種代幣給接收者,

    • 第四行和第五行是將結果排下序(因為交易對回傳的提取代幣數量的前后順序是按代幣地址從小到大排序的),使輸出引數匹配輸入引數的順序,

    • 第六行和第七行是確保提取的數量不能小于用戶指定的下限,否則重置交易,為什么會有這個保護呢,因為提取前可以存在多個交易,使交易對的兩種代幣比值(價格)和數量發生改變,從而達不到用戶的預期值,

    • 用戶呼叫該函式之前同樣需要給Router合約交易對流動性代幣的一定授權額度,因為中間使用到了授權交易transferFrom

  • removeLiquidityETH函式,同removeLiquidity函式類似,函式名多了ETH,它代表著用戶希望最后接收到ETH,也就意味著該交易對必須為一個TOKEN/WETH交易對,只有交易對中包含了WETH代幣,才能提取交易對資產池中的WETH,然后再將WETH兌換成ETH給接收者,函式代碼為:

    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);
    }
    
    • 因為WETH的地址公開且已知,所以函式的輸入引數就只有一個ERC20代幣地址,相應的,其中的一個Token文字值也換成了ETH

    • 函式的第一行直接呼叫上一個函式removeLiquidity來進行流動性移除操作,只不過將提取資產的接收地址改成本合約,為什么呢?因為提取的是WETH,用戶希望得到ETH,所以不能直接提取給接收者,還要多一步WETH/ETH兌換操作,

      注意,在呼叫本合約的removeLiquidity函式程序中,msg.sender保持不變(在另一種智能合約編程語言Vyper語言中,這種場景下msg.sender會發生變化),

    • 函式的第二行將燃燒流動性提取的另一種ERC20代幣(非WETH)轉移給接收者,

    • 第三行將燃燒流動性提取的WETH換成ETH,

    • 第四行將兌換的ETH發送給接收乾,

    • 因為呼叫了removeLiquidity函式,同樣需要用戶事先進行授權,見removeLiquidity函式分析,

  • removeLiquidityWithPermit函式,同樣也是移除流動性,同時提取交易對資產池中的兩種ERC20代幣,它和removeLiquidity函式的區別在于本函式支持使用線下簽名訊息來進行授權驗證,從而不需要提前進行授權(這樣會有一個額外交易),授權和交易均發生在同一個交易里,參考系列文章中的核心合約學習二中的permit函式學習,函式代碼為:

    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountA, uint amountB) {
        address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountA, amountB) = removeLiquidity(tokenA, tokenB, liquidity, amountAMin, amountBMin, to, deadline);
    }
    
    • removeLiquidity函式相比,它輸入引數多了bool approveMaxuint8 v, bytes32 r, bytes32 sapproveMax的含義為是否授權為uint256最大值(2 ** 256 -1),如果授權為最大值,在授權交易時有特殊處理,不再每次交易減少授權額度,相當于節省gas,這個核心合約學習二中也有提及,v,r,s用來和重建后的簽名訊息一起驗證簽名者地址,具體見核心合約學習二中的permit函式學習,

    • 函式的第一行照例是計算交易對地址,注意不會為零地址,

    • 函式的第二行用來根據是否為最大值設定授權額度,

    • 函式的第三行呼叫交易對合約的permit函式進行授權,

    • 函式的第四行呼叫removeLiquidity函式進行燃燒流動性從而提取代幣的操作,因為在第三行代碼里已經授權了,所以這里和前兩個函式有區別,不需要用戶提前進行授權了,

  • removeLiquidityETHWithPermit函式,功能同removeLiquidityWithPermit類似,只不過將最后提取的資產由TOKEN變為ETH,代碼可以比對removeLiquidityETH函式,因此這里大家可以自己學習一下,只是貼出函式代碼:

    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external virtual override returns (uint amountToken, uint amountETH) {
        address pair = UniswapV2Library.pairFor(factory, token, WETH);
        uint value = approveMax ? uint(-1) : liquidity;
        IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
        (amountToken, amountETH) = removeLiquidityETH(token, liquidity, amountTokenMin, amountETHMin, to, deadline);
    }
    
  • removeLiquidityETHSupportingFeeOnTransferTokens函式,名字很長,從函式名字中可以看到,它支持使用轉移的代幣支付手續費(支持包含此類代幣交易對),

    為什么會有使用轉移的代幣支付手續費這種提法呢?假定用戶有某種代幣,他想轉給別人,但他還必須同時有ETH來支付手續費,也就是它需要有兩種幣,轉的幣和支付手續費的幣,這就大大的提高了人們使用代幣的門檻,于是有人想到,可不可以使用轉移的代幣來支付手續費呢?有人也做了一些探索,由此衍生了一種新型別的代幣,ERC865代幣,它也是ERC20代幣的一個變種,ERC865代幣的詳細描述見ERC865: Pay transfer fees with tokens instead of ETH,

    然而本合約中的可支付轉移手續費的代幣卻并未指明是ERC865代幣,但是不管它是什么代幣,我們可以簡化為一點:此類代幣在轉移程序中可能發生損耗(損耗部分發送給第三方以支付整個交易的手續費),因此用戶發送的代幣數量未必就是接收者收到的代幣數量,

    本函式的功能和removeLiquidityETH函式相同,但是支持使用token支付費用,函式的代碼為:

    // **** REMOVE LIQUIDITY (supporting fee-on-transfer tokens) ****
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) public virtual override ensure(deadline) returns (uint amountETH) {
        (, amountETH) = removeLiquidity(
            token,
            WETH,
            liquidity,
            amountTokenMin,
            amountETHMin,
            address(this),
            deadline
        );
        TransferHelper.safeTransfer(token, to, IERC20(token).balanceOf(address(this)));
        IWETH(WETH).withdraw(amountETH);
        TransferHelper.safeTransferETH(to, amountETH);
    }
    

    我們將它的代碼和removeLiquidityETH函式的代碼相比較,只有稍微不同:

    1. 函式回傳引數及removeLiquidity函式回傳值中沒有了amountToken,因為它的一部分可能要支付手續費,所以removeLiquidity函式的回傳值不再為當前接收到的代幣數量,
    2. 不管損耗多少,它把本合約接收到的所有此類TOKEN直接發送給接收者,
    3. WETH不是可支付轉移手續費的代幣,因此它不會有損耗,
  • removeLiquidityETHWithPermitSupportingFeeOnTransferTokens函式,功能同removeLiquidityETHSupportingFeeOnTransferTokens函式相同,但是支持使用鏈下簽名訊息進行授權,本函式的代碼片斷為:

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline,
          bool approveMax, uint8 v, bytes32 r, bytes32 s
      ) external virtual override returns (uint amountETH) {
          address pair = UniswapV2Library.pairFor(factory, token, WETH);
          uint value = approveMax ? uint(-1) : liquidity;
          IUniswapV2Pair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
          amountETH = removeLiquidityETHSupportingFeeOnTransferTokens(
              token, liquidity, amountTokenMin, amountETHMin, to, deadline
          );
      }
    

    參照前面的函式學習可以很容易的看出本函式的代碼邏輯,這里大家自己嘗試一下,

四、流動性供給介面分類

原始碼中流動性供給的外部介面可以按照是提供流動性還是移除流動性分為兩大類,然后再根據初始資產/最終得到資產是ETH還是普通ERC20代幣做了進一步區分,然后移除流動性還增加了支持鏈下簽名訊息授權的介面,最后移除流動性增加了支持使用轉移代幣支付手續費的介面,

注:下文中的TOKEN均為ERC20代幣,

4.1、增加流動性

  1. addLiquidity,增加流動性,提供的初始資產為TOKEN/TOKEN,
  2. addLiquidityETH,增加流動性,提供的初始資產為ETH/TOKEN,

4.2、移除流動性

  1. removeLiquidity,移除流動性,得到的最終資產為TOKEN/TOKEN,
  2. removeLiquidityETH,移除流動性,得到的最終資產為ETH/TOKEN,

4.3、移除流動性,支持使用鏈下簽名訊息授權

  1. removeLiquidityWithPermit函式,移除流動性,支持使用鏈下簽名訊息授權,得到TOKEN/TOKEN,
  2. removeLiquidityETHWithPermit函式,移除流動性,支持使用鏈下簽名訊息授權,得到ETH/TOKEN,

4.4、移除流動性,支持使用轉移代幣支付手續費

  1. removeLiquidityETHSupportingFeeOnTransferTokens函式,移除流動性,支持使用轉移代幣支付手續費,得到ETH/TOKEN,

4.5、移除流動性,同時支持使用鏈下簽名訊息授權和使用轉移代幣支付手續費

  1. removeLiquidityETHWithPermitSupportingFeeOnTransferTokens函式,功能同標題,得到ETH/TOKEN,

從上面分類也可以得出一些其它結論,

  1. 增加流動性沒有使用鏈下簽名訊息授權,為什么呢?因為增加流動性其流動性代幣是直接增發,沒有使用第三方轉移,所以就沒有授權操作,不需要permit

  2. 移除流動性時,支付使用轉移代幣支付手續費最后得到的一種資產為ETH,說明交易對為ERC20/WETH交易對,也就是不支持兩個此類代幣構成的交易對,原因未知,還需要進一步研究,

  3. 既然移除流動性有使用轉移代幣支付手續費,那么作為同一個交易對,移除流動性之前必定有增加流動性,因此增加流動性時實際上需要支持此類代幣的,但是代碼中又沒有明確寫出支持使用轉移代幣支付手續費介面,為什么呢?

    個人猜想,未必正確:

    • 是因為此類代幣轉移程序中有損耗,而損耗多少未知,所以無法精確知道到底要提前轉移多少代幣到交易對中,在進行按比例計算時會得到預期外的值,所以寫此類介面無法向用戶回傳相關數量值,
    • 如果用戶不考慮回傳值的話,直接使用addLiquidity或者addLiquidityETH函式是可以對此類代幣進行增加流動性操作的,因為交易對計算注入代幣的數量時是以交易對合約地址當前代幣余額減去交易對合約資產池中的代幣余額,和損耗沒有任何關系,因此,增發的流動性是準確的,

至此,UniswapV2Router02.sol學習(上)–流動性借給函式的學習就到此結束了,下一次計劃學習UniswapV2Router02.sol(下)–資產交易函式的學習,

由于個人能力有限,難免有理解錯誤或者不正確的地方,還請大家多多留言指正,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/183246.html

標籤:其他

上一篇:動態鏈接 & 靜態鏈接(區別及優缺點)

下一篇:邊城科技“區塊鏈+溯源”平臺助力坪朗豆腐品牌升級

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • vue移動端上拉加載

    可能做得過于簡單或者比較low,請各位大佬留情,一起探討技術 ......

    uj5u.com 2020-09-10 04:38:07 more
  • 優美網站首頁,頂部多層導航

    一個個人用的瀏覽器首頁,可以把一下常用的網站放在這里,平常打開會比較方便。 第一步,HTML代碼 <script src=https://www.cnblogs.com/szharf/p/"js/jquery-3.4.1.min.js"></script> <div id="navigate"> <ul> <li class="labels labels_1"> ......

    uj5u.com 2020-09-10 04:38:47 more
  • 頁面為要加<!DOCTYPE html>

    最近因為寫一個js函式,需要用到$(window).height(); 由于手寫demo的時候,過于自信,其實對前端方面的認識也不夠體系,用文本檔案直接敲出來的html代碼,第一行沒有加上<!DOCTYPE html> 導致了$(window).height();的結果直接是整個document的高 ......

    uj5u.com 2020-09-10 04:38:52 more
  • WordPress網站程式手動升級要做好資料備份

    WordPress博客網站程式在進行升級前,必須要做好網站資料的備份,這個問題良家佐言是遇見過的;在剛開始接觸WordPress博客程式的時候,因為升級問題和博客網站的修改的一些嘗試,良家佐言是吃盡了苦頭。因為購買的是西部數碼的空間和域名,每當佐言把自己的WordPress博客網站搞到一塌糊涂的時候 ......

    uj5u.com 2020-09-10 04:39:30 more
  • WordPress程式不能升級為5.4.2版本的原因

    WordPress是一款個人博客系統,受到英文博客愛好者和中文博客愛好者的追捧,并逐步演化成一款內容管理系統軟體;它是使用PHP語言和MySQL資料庫開發的,用戶可以在支持PHP和MySQL資料庫的服務器上使用自己的博客。每一次WordPress程式的更新,就會牽動無數WordPress愛好者的心, ......

    uj5u.com 2020-09-10 04:39:49 more
  • 使用CSS3的偽元素進行首字母下沉和首行改變樣式

    網頁中常見的一種效果,首字改變樣式或者首行改變樣式,效果如下圖。 代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, ......

    uj5u.com 2020-09-10 04:40:09 more
  • 關于a標簽的講解

    什么是a標簽? <a> 標簽定義超鏈接,用于從一個頁面鏈接到另一個頁面。 <a> 元素最重要的屬性是 href 屬性,它指定鏈接的目標。 a標簽的語法格式:<a href=https://www.cnblogs.com/summerxbc/p/"指定要跳轉的目標界面的鏈接">需要展示給用戶看見的內容</a> a標簽 在所有瀏覽器中,鏈接的默認外觀如下: 未被訪問的鏈接帶 ......

    uj5u.com 2020-09-10 04:40:11 more
  • 前端輪播圖

    在需要輪播的頁面是引入swiper.min.js和swiper.min.css swiper.min.js地址: 鏈接:https://pan.baidu.com/s/15Uh516YHa4CV3X-RyjEIWw 提取碼:4aks swiper.min.css地址 鏈接:https://pan.b ......

    uj5u.com 2020-09-10 04:40:13 more
  • 如何設定html中的背景圖片(全屏顯示,且不拉伸)

    1 <style>2 body{background-image:url(https://uploadbeta.com/api/pictures/random/?key=BingEverydayWallpaperPicture); 3 background-size:cover;background ......

    uj5u.com 2020-09-10 04:40:16 more
  • Java學習——HTML詳解(上)

    HTML詳解 初識HTML Hyper Text Markup Language(超文本標記語言) 1 <!--DOCTYPE:告訴瀏覽器我們要使用什么規范--> 2 <!DOCTYPE html> 3 <html lang="en"> 4 <head> 5 <!--meta 描述性的標簽,描述一些 ......

    uj5u.com 2020-09-10 04:40:33 more
最新发布
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 07:59:23 more
  • 生產事故-走近科學之消失的JWT

    入職多年,面對生產環境,盡管都是小心翼翼,慎之又慎,還是難免捅出簍子。輕則滿頭大汗,面紅耳赤。重則系統停擺,損失資金。每一個生產事故的背后,都是寶貴的經驗和教訓,都是專案成員的血淚史。為了更好地防范和遏制今后的各類事故,特開此專題,長期更新和記錄大大小小的各類事故。有些是親身經歷,有些是經人耳傳口授 ......

    uj5u.com 2023-04-18 07:55:04 more
  • 記錄--Canvas實作打飛字游戲

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 打開游戲界面,看到一個畫面簡潔、卻又富有挑戰性的游戲。螢屏上,有一個白色的矩形框,里面不斷下落著各種單詞,而我需要迅速地輸入這些單詞。如果我輸入的單詞與螢屏上的單詞匹配,那么我就可以獲得得分;如果我輸入的單詞錯誤或者時間過長,那么我就會輸 ......

    uj5u.com 2023-04-04 08:35:30 more
  • 了解 HTTP 看這一篇就夠

    在學習網路之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網路的興趣。下面的這張圖片就展示了“互聯網”誕生至今的發展歷程。 ......

    uj5u.com 2023-03-16 11:00:15 more
  • 藍牙-低功耗中心設備

    //11.開啟藍牙配接器 openBluetoothAdapter //21.開始搜索藍牙設備 startBluetoothDevicesDiscovery //31.開啟監聽搜索藍牙設備 onBluetoothDeviceFound //30.停止監聽搜索藍牙設備 offBluetoothDevi ......

    uj5u.com 2023-03-15 09:06:45 more
  • canvas畫板(滑鼠和觸摸)

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>canves</title> <style> #canvas { cursor:url(../images/pen.png),crosshair; } #canvasdiv{ bo ......

    uj5u.com 2023-02-15 08:56:31 more
  • 手機端H5 實作自定義拍照界面

    手機端 H5 實作自定義拍照界面也可以使用 MediaDevices API 和 <video> 標簽來實作,和在桌面端做法基本一致。 首先,使用 MediaDevices.getUserMedia() 方法獲取攝像頭媒體流,并將其傳遞給 <video> 標簽進行渲染。 接著,使用 HTML 的 < ......

    uj5u.com 2023-01-12 07:58:22 more
  • 記錄--短視頻滑動播放在 H5 下的實作

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 短視頻已經無數不在了,但是主體還是使用 app 來承載的。本文講述 H5 如何實作 app 的視頻滑動體驗。 無聲勝有聲,一圖頂百辯,且看下圖: 網址鏈接(需在微信或者手Q中瀏覽) 從上圖可以看到,我們主要實作的功能也是本文要講解的有: ......

    uj5u.com 2023-01-04 07:29:05 more
  • 一文讀懂 HTTP/1 HTTP/2 HTTP/3

    從 1989 年萬維網(www)誕生,HTTP(HyperText Transfer Protocol)經歷了眾多版本迭代,WebSocket 也在期間萌芽。1991 年 HTTP0.9 被發明。1996 年出現了 HTTP1.0。2015 年 HTTP2 正式發布。2020 年 HTTP3 或能正... ......

    uj5u.com 2022-12-24 06:56:02 more
  • 【HTML基礎篇002】HTML之form表單超詳解

    ??一、form表單是什么

    ??二、form表單的屬性

    ??三、input中的各種Type屬性值

    ??四、標簽 ......

    uj5u.com 2022-12-18 07:17:06 more