Uniswap V2 — 從代碼解釋 DeFi 協議
為了理解我們在分析代碼時將要經歷的不同組件,首先了解哪些是主要概念以及它們的作用是很重要的,所以,和我一起裸露吧,因為這是值得的,
我在 5 個段落中總結了您需要了解的主要重要概念,您將在本文結束時理解這些概念,
Uniswap 是一種去中心化交易協議,該協議是一套持久的、不可升級的智能合約,它們共同創建了一個自動化的做市商,
Uniswap 生態系統由貢獻流動性的流動性提供者、交換代幣的交易員和與智能合約互動以開發代幣新互動的開發人員組成,
每個 Uniswap智能合約或對管理一個由兩個 ERC-20 代幣儲備組成的流動資金池,
每個流動性池重新平衡以保持 50/50 比例的加密貨幣資產,這反過來又決定了資產的價格,
流動性提供者可以是任何能夠向 Uniswap 交易合約提供等值的 ETH 和 ERC-20 代幣的人,作為回報,他們從交易合約中獲得流動性提供者代幣(LP 代幣代表流動性提供者擁有的池的份額),可用于隨時提取其在流動性池中的比例,
他們存盤庫中的主要智能合約是:
UniswapV2ERC20
— 用于 LP 令牌的擴展 ERC20 實作,它還實施了 EIP-2612 以支持鏈下傳輸批準,UniswapV2Factory
— 與 V1 類似,這是一個工廠合約,它創建配對合約并充當它們的注冊表,注冊表使用 create2 來生成對地址——我們將詳細了解它是如何作業的,UniswapV2Pair
— 負責核心邏輯的主合約,值得注意的是,工廠只允許創建獨特的貨幣對,以免稀釋流動性,UniswapV2Router
— Uniswap UI 和其他在 Uniswap 之上作業的網路和去中心化應用程式的主要入口點,UniswapV2Library
— 一組實作重要計算的輔助函式,
在這篇文章中,我們將提及所有這些,但我們將主要關注瀏覽UniswapV2Router
和UniswapV2Factory
編碼,盡管UniswapV2Pair
并且UniswapV2Library
會涉及很多,
UniswapV2Router02.sol
該合約使創建貨幣對、添加和洗掉流動性、計算所有可能的掉期變化的價格以及執行實際掉期變得更加容易,路由器適用于通過工廠合約部署的所有對
您需要在合約中創建一個實體才能呼叫 addLiquidity、removeLiquidity 和 swapExactTokensForTokens 函式
address private constant ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
IUniswapV2Router02 public uniswapV2Router;
uniswapV2Router = IUniswapV2Router02(ROUTER);
現在讓我們看看流動性管理:
函式 addLiquidity():
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
- tokenA和tokenB:是我們需要獲取或創建我們想要增加流動性的貨幣對的代幣,
- amountADesired和amountBDesired是我們要存入流動資金池的金額,
- amountAMin和amountBMin是我們要存入的最小金額,
- to address 是接收 LP 代幣的地址,
- 截止日期,最常見的是
block.timestamp
在內部 _addLiquidity() 中,它將檢查這兩個令牌中的一對是否已經存在,如果不存在,它將創建一個新令牌
if (IUniswapV2Factory(factory).getPair(tokenA, tokenB) == address(0)) {
IUniswapV2Factory(factory).createPair(tokenA, tokenB);
}
然后它需要獲取現有的代幣數量或也稱為reserveA
and reserveB
,我們可以通過 UniswapV2Pair 合約訪問它
IUniswapV2Pair(pairFor(factory, tokenA, tokenB)).getReserves()
現在,外部函式 addLiquidity, 回傳(uint amountA, uint amountB, uint liquidity)
,那么它是如何計算的呢?
通過UniswapV2Library拿到上面提到的reserves之后,還有一系列的檢查
如果該對不存在,并且新創建 amountA
并amountB
回傳一個新的,則將amountADesired
作為amountBDesired
引數傳遞(見上文),
否則,它會做這個操作
amountBOptimal = amountADesired.mul(reserveB) / reserveA;
如果amountB
小于或等于,amountBDesired
那么它將回傳:
(uint amountA, uint amountB) = (amountADesired, amountBOptimal)
否則,它將回傳
(uint amountA, uint amountB) = (amountAOptimal, amountBDesired)
其中amountAOptimal
的計算方式與amountBOptimal
然后,要計算liquidity
回傳值將經過以下程序:
首先,它將使用現有/新創建的對的地址部署 UniswapV2Pair 合約,
它是如何做到的?它計算一對的 CREATE2 地址而無需進行任何外部呼叫:(閱讀有關 CREATE2 Opcode 的更多資訊)
pair = address(uint(keccak256(abi.encodePacked(address(uint(keccak256(abi.encodePacked(
hex'ff',
factory,
keccak256(abi.encodePacked(token0, token1)),
hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
))));
然后,它獲取新部署合約的地址,我們需要用它來從這對代幣中鑄造代幣,
當您向貨幣對添加流動性時,合約會生成 LP 代幣;當你移除流動性時,LP 代幣就會被銷毀,
pairFor
因此,首先我們使用UniswapV2Library獲取地址:
address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB);UniswapV2Library.pairFor(factory, tokenA, tokenB);
因此,稍后可以鑄造 ERC20 代幣并計算回傳的流動性:
liquidity = IUniswapV2Pair(pair).mint(to);
如果您想知道為什么它最終成為 ERC20,在 mint 函式中它是這樣存盤的https://github.com/Uniswap/v2-core/blob/ee547b17853e71ed4e0101ccfd52e70d5acded58/contracts/UniswapV2Pair.sol#L112)
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
****函式removeLiquidity():
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB);
從池中移除流動性意味著燃燒 LP 代幣以換取一定數量的基礎代幣,
IUniswapV2Pair(pair).transferFrom(msg.sender, pair, liquidity);
然后,外部函式回傳兩個值(uint amountA, uint amountB)
,這些值是使用傳遞給函式的引數計算的,
隨提供的流動性回傳的代幣數量計算如下:
amount0 = liquidity.mul(balance0) / _totalSupply;
amount1 = liquidity.mul(balance1) / _totalSupply;
然后它將這些數量的代幣轉移到指定的地址
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
您的 LP 代幣份額越大,銷毀后獲得的儲備份額就越大,
上面的這些計算發生在 burn 函式內部
IUniswapV2Pair(對).burn(對)
https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/UniswapV2Router02.sol#L114)
IUniswapV2Pair(pair).burn(to)
****函式swapExactTokensForTokens()
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
Uniswap 的核心功能是交換代幣,所以讓我們弄清楚代碼中發生了什么,以便更好地理解它
您很可能聽說過流動資金池中使用的神奇公式
X * Y = K
所以,這將首先發生在 swap 函式內部getAmountOut()
,
里面用到的關鍵函式有:
TransferHelper.safeTransferFrom().safeTransferFrom()
代幣金額發送到配對代幣的地方
在 UniswapV2Pair 合約的較低級別交換功能中,它將是
_safeTransfer(_token, to, amountOut);
這將實際轉移回預期地址,
我知道資訊量很大,但您將有足夠的時間閱讀所有內容,直到完全理解為止,所以……
UniswapV2Factory.sol
工廠合約是所有已部署對合約的注冊表,這個合約是必要的,因為我們不希望有成對的相同代幣,這樣流動性就不會分成多個相同的對,
該合約還簡化了配對合約的部署:無需通過任何外部呼叫手動部署配對合約,只需呼叫工廠合約中的方法即可,
好吧,讓我們倒回去,因為在上面的這些行中已經說了非常重要的事情,我們把它們拆分開來分別分析:
該合約是所有已部署對合約的注冊表
只部署了一個工廠合約,該合約用作 Uniswap 交易對的官方注冊處,
現在,我們在代碼中的什么地方看到了它以及發生了什么:
address[] public allPairs;
它有 的陣列allPairs
,如上所述,存盤在這個合約中,這些對被添加到一個方法中,該方法createPair()
通過將新初始化的對推送到陣列來呼叫,
allPairs.push(pair);push(pair);
這個合約是必要的,因為我們不想擁有成對的相同代幣
mapping(address => mapping(address => address)) public getPair;
它具有該對的地址與構成該對的兩個令牌的映射,這用于檢查一對是否已經存在,
require(getPair[token0][token1] == address(0), 'UniswapV2: PAIR_EXISTS');
該合約還簡化了配對合約的部署
這是一個更深層次的話題,但我將嘗試總結一下這里發生的事情的重要性,
在以太坊中,合約可以部署合約,可以呼叫已部署合約的函式,該函式將部署另一個合約,
您不需要從您的計算機上編譯和部署合約,您可以通過現有合約來執行此操作,
那么,Uniswap 是如何部署智能合約的呢?
通過使用操作碼CREATE2
bytes memory bytecode = type(UniswapV2Pair).creationCode;type(UniswapV2Pair).creationCode;
bytes32 salt = keccak256(abi.encodePacked(token0, token1));
assembly {
pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
}
在第一行,我們得到創建位元組碼UniswapV2Pair
下一行創建了salt
一個位元組序列,用于確定性地生成新合約的地址,
最后一行是我們呼叫以使用+create2
確定性地創建新地址的地方,部署,bytecode``salt``UniswapV2Pair
并得到對地址,我們可以看到這是createPair()
函式的回傳值
function createPair(
address tokenA, address tokenA,
address tokenB
) external returns (address pair)
當提供的標記不是現有的對_addLiquidity()
時,它在內部函式中使用,
所以,這就是關于 Uniswap 代碼的全部內容,
現在,為了看到我們測驗的所有內容,我可以推薦您查看 Smart Contract Programmer 在他的defi-by-example 內容中實作的代碼,他已經在視頻中進行了解釋,
在這里你可以看到我們可以增加流動性的方式:
function addLiquidity(
address _tokenA,
address _tokenB,
uint _amountA,
uint _amountB
) external {
IERC20(_tokenA).transferFrom(msg.sender, address(this), _amountA);
IERC20(_tokenB).transferFrom(msg.sender, address(this), _amountB);
IERC20(_tokenA).approve(ROUTER, _amountA);
IERC20(_tokenB).approve(ROUTER, _amountB);
(uint amountA, uint amountB, uint liquidity) =
IUniswapV2Router(ROUTER).addLiquidity(
_tokenA,
_tokenB,
_amountA,
_amountB,
1,
1,
address(this),
block.timestamp
);
emit Log("amountA", amountA);
emit Log("amountB", amountB);
emit Log("liquidity", liquidity);
}
以及我們必須如何考慮消除流動性:
function removeLiquidity(address _tokenA, address _tokenB) external {
address pair = IUniswapV2Factory(FACTORY).getPair(_tokenA, _tokenB);
uint liquidity = IERC20(pair).balanceOf(address(this));
IERC20(pair).approve(ROUTER, liquidity);
(uint amountA, uint amountB) =
IUniswapV2Router(ROUTER).removeLiquidity(
_tokenA,
_tokenB,
liquidity,
1,
1,
address(this),
block.timestamp
);
emit Log("amountA", amountA);
emit Log("amountB", amountB);
}
通過Github 獲取更多區塊鏈學習資料!
https://github.com/Manuel-yang/BlockChainSelfLearning
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/550867.html
標籤:區塊鏈
上一篇:web3 產品介紹: safe --多簽錢包 多人審批更放心
下一篇:返回列表