主頁 >  其他 > UniswapV2周邊合約學習(二)-- UniswapV2Router02.sol(上)

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

2020-10-21 05:20:46 其他

記得朋友圈看到過一句話,如果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/qita/183350.html

標籤:其他

上一篇:vim taglist學習筆記

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

標籤雲
其他(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)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more