//參考自:https://blog.csdn.net/weixin_39430411/article/details/108965855
//指定版本
pragma solidity =0.5.16;
//匯入Pair介面
import './interfaces/IUniswapV2Pair.sol';
//匯入V2ERC20合約
import './UniswapV2ERC20.sol';
//匯入一個數學庫,包含一個min()函式,一個sqrt()函式
import './libraries/Math.sol';
//?
import './libraries/UQ112x112.sol';
//匯入IERC20合約
import './interfaces/IERC20.sol';
//匯入Factory,?
import './interfaces/IUniswapV2Factory.sol';
//?
import './interfaces/IUniswapV2Callee.sol';
//?
contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 {
using SafeMath for uint;
using UQ112x112 for uint224;
//設定最小流動性,用于燃燒
uint public constant MINIMUM_LIQUIDITY = 10**3;
//一個選擇器,不懂
bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
//記錄工廠地址、兩種代幣的地址
address public factory;
address public token0;
address public token1;
//記錄兩種代幣的儲備量
uint112 private reserve0; // uses single storage slot, accessible via getReserves
uint112 private reserve1; // uses single storage slot, accessible via getReserves
//記錄交易時的區塊鏈時間
uint32 private blockTimestampLast; // uses single storage slot, accessible via getReserves
//記錄價格最新的累計值
uint public price0CumulativeLast;
uint public price1CumulativeLast;
//記錄token0和token1儲備量的乘積,在最新的流動性事件發生之后記錄
uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event
//防重入攻擊的設計
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, 'UniswapV2: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
//獲得交易對的儲備量,回傳值包含了兩種代幣的儲備量、交易時區塊的時間
function getReserves() public view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast) {
_reserve0 = reserve0;
_reserve1 = reserve1;
_blockTimestampLast = blockTimestampLast;
}
//一個safe版的transfer,實作函式呼叫者將token幣轉給to用戶的功能,轉賬數量為value
function _safeTransfer(address token, address to, uint value) private {
//對已編碼的transfer函式、選擇器、轉賬目標的地址、轉賬數量進行call(),call成功回傳true,其余的目前還不懂
//總之是一個safe版的transfer
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED');
}
//挖礦事件:sender挖了兩種幣?
event Mint(address indexed sender, uint amount0, uint amount1);
//燒幣事件:sender燒了to兩種幣?
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
//交換事件:sender注入和取出兩種幣的數量,取出到to?
event Swap(
address indexed sender,
uint amount0In,
uint amount1In,
uint amount0Out,
uint amount1Out,
address indexed to
);
//同步事件:更新了兩種幣的儲備量
event Sync(uint112 reserve0, uint112 reserve1);
//初始化時的呼叫者即為factory
constructor() public {
factory = msg.sender;
}
//對呼叫此函式的factory進行初始化,初始化時提交交易對的兩種token地址
// called once by the factory at time of deployment
function initialize(address _token0, address _token1) external {
//驗證呼叫者是否為一個factory
require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check
token0 = _token0;
token1 = _token1;
}
//更新儲備量,并且在每個區塊第一次呼叫時,將價格進行累計,暫時不懂
// update reserves and, on the first call per block, price accumulators
//提交兩種代幣余額、計算K的兩種儲備量的值
//函式會將保存的數值更新為實時代幣余額,并進行價格累計的計算?
function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private {
//保證余額小于uint112的最大值,因為余額時uint256
require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW');
//記錄當前區塊的時間,將之轉化為32的形式存盤
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
//記錄已經過去的時間,溢位已經在預期中,待思考
uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired
//如果已經過去的時間大于零,且兩資產量不為0
if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) {
//永遠不會溢位,并且+溢位已在預期中,待思考
// * never overflows, and + overflow is desired
//在UQ112x112.sol中
//encode()將uint112轉化為一個uint112x112
//updiv()用引數一/引數二,得到一個UQ112x112
//不明白為什么要計算這樣一個累計值
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
//更新儲備量和時間
reserve0 = uint112(balance0);
reserve1 = uint112(balance1);
blockTimestampLast = blockTimestamp;
//記錄同步的儲備量
emit Sync(reserve0, reserve1);
}
//該函式用來挖取手續費中獎勵給開發者的部分,為了節省gas,不會在每次交易時都計算開發者分成,
//只有在改變流動性時,才會將積累的手續費進行分成計算
// if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
//提交兩種代幣的儲備量,回傳一個布林值記錄手續費的分成是否開啟
function _mintFee(uint112 _reserve0, uint112 _reserve1) private returns (bool feeOn) {
//記錄接收分成的地址,該地址為初始化時記錄的factory,即部署者
//feeTo()為factory中公共變數feeTo自動生成的函式
address feeTo = IUniswapV2Factory(factory).feeTo();
//如果feeTO的地址不為空地址,即代表手續費分成的開關打開
feeOn = feeTo != address(0);
//記錄下最新的兩儲備量乘積K
uint _kLast = kLast; // gas savings
//如果分成的開關打開,則需要計算分成的數額,根據參考的博客提供的官方公式,
//分成以增發流動性代幣的形式分配,公式內容即為下面的liqudity所顯示的計算方法
if (feeOn) {
//如果K不為0,表明分成開關打開后,曾記錄過一次K
if (_kLast != 0) {
//對儲備量的乘積開平方(新的K)
uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1));
//對K系數開平方(舊的K)
uint rootKLast = Math.sqrt(_kLast);
//如果新K大于舊K
if (rootK > rootKLast) {
//totalSupply為V2REC20中的公共變數,其代表Pair在產生流動性時,同步發出的記錄流動性的token
//分子 = totalSupply * (K - KLast)
uint numerator = totalSupply.mul(rootK.sub(rootKLast));
//分母 = K * 5 + KLast
uint denominator = rootK.mul(5).add(rootKLast);
//流動性 = 分子 / 分母
uint liquidity = numerator / denominator;
//如果流動性大于零,給feeTo地址,liquidity數量的V2ERC20,_mint()為V2ERC20的函式
if (liquidity > 0) _mint(feeTo, liquidity);
}
}
}
//如果分成開關關閉,且K不為零
else if (_kLast != 0) {
//將K改為0,防止以后計算時仍使用非常早的舊值
kLast = 0;
}
}
//這一個應該被從其他合約中呼叫的低等級函式,在呼叫的合約中需要執行重要的安全檢查
// this low-level function should be called from a contract which performs important safety checks
//遞交一個地址,給該地址發放一定量的流動性,回傳值為發放的流動性數量
function mint(address to) external lock returns (uint liquidity) {
//獲取交易對的儲備量
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
//讀取本地交易對余額
uint balance0 = IERC20(token0).balanceOf(address(this));
uint balance1 = IERC20(token1).balanceOf(address(this));
//余額減去儲備量,得到注入了多少資產,這說明資產已經注入
uint amount0 = balance0.sub(_reserve0);
uint amount1 = balance1.sub(_reserve1);
//提交兩種代幣的數量,得到分成開關是否開啟,若開啟,手續費已發送給feeTo地址
bool feeOn = _mintFee(_reserve0, _reserve1);
//記錄下流動性代幣的總發行量
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
//若此前從未發行過流動性,說明這是第一次接收注入的pair
if (_totalSupply == 0) {
//流動性 = token1注入量*token1注入量的開方-MINIMUM_LIQUIDITY,即燃燒量
liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY);
//永久地鎖定第一筆燃燒token
_mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens
}
//若此前已發行過流動性
else {
//流動性的計算公式為:token0的注入量*總量/token0儲備量 - token1的注入量*總量/token1儲備量,待思考
liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1);
}
//保證流動性大于零
require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED');
//給to發布流動性代幣
_mint(to, liquidity);
//更新引數
_update(balance0, balance1, _reserve0, _reserve1);
//如果分成開關打開,更新K
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
//記錄呼叫該合約的外部合約地址,及注入的兩種代幣數量
emit Mint(msg.sender, amount0, amount1);
}
// this low-level function should be called from a contract which performs important safety checks
//燃燒to地址的代幣,提取流動性pair
function burn(address to) external lock returns (uint amount0, uint amount1) {
//獲取pair儲備量
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
//記錄兩種代幣地址
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
//獲取兩種代幣在Pair中的余額量
uint balance0 = IERC20(_token0).balanceOf(address(this));
uint balance1 = IERC20(_token1).balanceOf(address(this));
//獲取流動性代幣在本地的余額,即此時兌換方已將流動性代幣注入池子
uint liquidity = balanceOf[address(this)];
//獲取分成開關狀態,若開關為true,分成已發送給feeTo地址
bool feeOn = _mintFee(_reserve0, _reserve1);
//記錄流動性代幣總量
uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee
//應該兌換給對方的token0量 = 對方注入的流動性代幣量 * Pair中token0的總量 / 流動性代幣的總量
amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution
amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution
//保證兩種token的兌換量大于零
require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED');
//呼叫V2ERC20的_burn(),燃燒掉在pair池子中的liquidity數量的流動性代幣
_burn(address(this), liquidity);
//呼叫本合約中重新定義的safe版transfer,將兩種代幣轉給對方
_safeTransfer(_token0, to, amount0);
_safeTransfer(_token1, to, amount1);
//更新兩種代幣在本地的余額,區域變數
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
//更新儲備量,細節待考量
_update(balance0, balance1, _reserve0, _reserve1);
//如果分成開關打開,需要更新k系數
if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date
//記錄msg.sender取出流動性到to
emit Burn(msg.sender, amount0, amount1, to);
}
// this low-level function should be called from a contract which performs important safety checks
//該函式用于對不同token進行兌換,可形成交易鏈
//引數為(購買token0的數量,token1的數量,接收地址,傳遞的某種引數?)
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
//保證兌換代幣的數量大于零
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
//獲取pair中資產量
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
//確保對方想要兌換的數量小于池子中代幣的數量
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
//用于存盤兩種代幣的余額
uint balance0;
uint balance1;
//一塊關于token0和token1的區域,為了避免堆疊過深的錯誤,不太懂
{ // scope for _token{0,1}, avoids stack too deep errors
//從狀態變數中讀入兩種token的地址
address _token0 = token0;
address _token1 = token1;
//保證接收地址不是token的地址
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
//如果對token0、1的兌換量大于零,將之轉給to地址
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
//如果呼叫swap函式時傳入的引數有內容,對呼叫該函式的msg.sender發送一個資訊,包含了呼叫該函式的地址、
//兌換的token0、1的數量,以及傳入的引數
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
//更新兩代幣余額
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
//若代幣余額>(初始時讀取的儲備量-傳出量),則代幣In數量 = 代幣余額 - (初始時讀取的儲備量-傳出量),
//該值記錄了對方注入池子中的token量,
//這說明在函式剛執行時,(uint112 _reserve0, uint112 _reserve1,) = getReserves();
//得到的是之前的儲備量,并非是最新的儲備量
//若代幣余額<(初始時讀取的儲備量-傳出量),則該值為0
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
//保證兩值均大于零,否則說明對方并未注入token
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
//一塊用于reserve0、1的范圍,避免堆疊過深的錯誤
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
//調整后的token余額 = 余額數 * 1000 - 注入量 * 3
//根據參考的博客提供的官方公式:
//(balance0 - 0.003 * amount0In) * (balance1 - 0.003 * amount1In) >= reserve0 * reserve1
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
確保新K系數>=舊系數
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
//更新資料
_update(balance0, balance1, _reserve0, _reserve1);
//記錄swap事件
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
//(掠過)
//強制余額數量轉化為匹配的儲備數量,即將多余的資產(余額-儲備量)轉給回給to地址
// force balances to match reserves
function skim(address to) external lock {
//把狀態變數讀入
address _token0 = token0; // gas savings
address _token1 = token1; // gas savings
//把兩種代幣轉給to地址,轉出量為本地該代幣的余額總量減去儲備量
_safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0));
_safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1));
}
//(同步)
//強制儲備數量轉化為匹配的余額
// force reserves to match balances
function sync() external lock {
//?
_update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/287656.html
標籤:區塊鏈
