記得朋友圈看到過一句話,如果Defi是以太坊的皇冠,那么Uniswap就是這頂皇冠中的明珠,Uniswap目前已經是V2版本,相對V1,它的功能更加全面優化,然而其合約原始碼卻并不復雜,本文為個人學習UniswapV2原始碼的系列記錄文章,
一、Router合約介紹
UniswapV2的周邊合約主要作用是作為用戶和核心合約之間的橋梁,也就是用戶 => 周邊合約 => 核心合約,UniswapV2周邊合約主要包含介面定義,工具庫和核心實作這三部分,在上一篇文章里已經學習了它的工具庫函式,這次我們主要學習其核心實作,
UniswapV2周邊合約的核心實作包含UniswapV2Router01.sol和UniswapV2Router02.sol,這里我們把它簡稱為Router1和Router2,查看它們實作的介面我們可以看到,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合約的地址,這里有兩個關鍵詞immutable和override需要深入學習一下,-
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檔案也提到了三點注意事項:
- 為了覆寫所有場景,呼叫者需要給該
Router合約一定額度的兩種代幣授權,因為注入的資產為ERC20代幣,第三方合約如果不得到授權(或者授權額度不夠),就無法轉移你的代幣到交易對合約中去, - 總是按理想的比例注入代幣(因為計算比例和注入在一個交易內進行),具體取決于交易執行時的價格,這一點在介紹
_addLiquidity函式時已經講了, - 如果交易對不存在,則會自動創建,擬注入的代幣數量就是真正注入的代幣數量,
-
-
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 approveMax及uint8 v, bytes32 r, bytes32 s,approveMax的含義為是否授權為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函式的代碼相比較,只有稍微不同:- 函式回傳引數及
removeLiquidity函式回傳值中沒有了amountToken,因為它的一部分可能要支付手續費,所以removeLiquidity函式的回傳值不再為當前接收到的代幣數量, - 不管損耗多少,它把本合約接收到的所有此類TOKEN直接發送給接收者,
- 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、增加流動性
addLiquidity,增加流動性,提供的初始資產為TOKEN/TOKEN,addLiquidityETH,增加流動性,提供的初始資產為ETH/TOKEN,
4.2、移除流動性
removeLiquidity,移除流動性,得到的最終資產為TOKEN/TOKEN,removeLiquidityETH,移除流動性,得到的最終資產為ETH/TOKEN,
4.3、移除流動性,支持使用鏈下簽名訊息授權
removeLiquidityWithPermit函式,移除流動性,支持使用鏈下簽名訊息授權,得到TOKEN/TOKEN,removeLiquidityETHWithPermit函式,移除流動性,支持使用鏈下簽名訊息授權,得到ETH/TOKEN,
4.4、移除流動性,支持使用轉移代幣支付手續費
removeLiquidityETHSupportingFeeOnTransferTokens函式,移除流動性,支持使用轉移代幣支付手續費,得到ETH/TOKEN,
4.5、移除流動性,同時支持使用鏈下簽名訊息授權和使用轉移代幣支付手續費
removeLiquidityETHWithPermitSupportingFeeOnTransferTokens函式,功能同標題,得到ETH/TOKEN,
從上面分類也可以得出一些其它結論,
-
增加流動性沒有使用鏈下簽名訊息授權,為什么呢?因為增加流動性其流動性代幣是直接增發,沒有使用第三方轉移,所以就沒有授權操作,不需要
permit, -
移除流動性時,支付使用轉移代幣支付手續費最后得到的一種資產為ETH,說明交易對為ERC20/WETH交易對,也就是不支持兩個此類代幣構成的交易對,原因未知,還需要進一步研究,
-
既然移除流動性有使用轉移代幣支付手續費,那么作為同一個交易對,移除流動性之前必定有增加流動性,因此增加流動性時實際上需要支持此類代幣的,但是代碼中又沒有明確寫出支持使用轉移代幣支付手續費介面,為什么呢?
個人猜想,未必正確:
- 是因為此類代幣轉移程序中有損耗,而損耗多少未知,所以無法精確知道到底要提前轉移多少代幣到交易對中,在進行按比例計算時會得到預期外的值,所以寫此類介面無法向用戶回傳相關數量值,
- 如果用戶不考慮回傳值的話,直接使用
addLiquidity或者addLiquidityETH函式是可以對此類代幣進行增加流動性操作的,因為交易對計算注入代幣的數量時是以交易對合約地址當前代幣余額減去交易對合約資產池中的代幣余額,和損耗沒有任何關系,因此,增發的流動性是準確的,
至此,UniswapV2Router02.sol學習(上)–流動性借給函式的學習就到此結束了,下一次計劃學習UniswapV2Router02.sol(下)–資產交易函式的學習,
由于個人能力有限,難免有理解錯誤或者不正確的地方,還請大家多多留言指正,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/183350.html
標籤:其他
上一篇:vim taglist學習筆記
