1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
| pragma solidity =0.5.16;
import './interfaces/IUniswapV2Pair.sol'; import './UniswapV2ERC20.sol'; //自定义的Math库,只有两个功能,一个是求两个uint的最小值,另一个是对一个uint进行开方运算。 import './libraries/Math.sol'; //导入自定义的数据格式库。在UniswapV2中,价格为两种代币的数量比值,而在Solidity中,对非整数类型支持不好,通常两个无符号整数相除为地板除,会截断。为了提高价格精度,UniswapV2使用uint112来保存交易对中资产的数量,而比值(价格)使用UQ112x112表示,一个代表整数部分,一个代表小数部分。 import './libraries/UQ112x112.sol'; //导入标准ERC20接口,在获取交易对合约资产池的代币数量(余额)时使用。 import './interfaces/IERC20.sol'; //导入factory合约相关接口,主要是用来获取开发团队手续费地址。 import './interfaces/IUniswapV2Factory.sol'; //有些第三方合约希望接收到代币后进行其它操作,好比异步执行中的回调函数。这里IUniswapV2Callee约定了第三方合约如果需要执行回调函数必须实现的接口格式。当然了,定义了此接口后还可以进行FlashSwap。 import './interfaces/IUniswapV2Callee.sol';
//继承一个合约表明它继承了父合约的所有非私有的接口与状态变量。 contract UniswapV2Pair is IUniswapV2Pair, UniswapV2ERC20 { //指定库函数的应用类型 using SafeMath for uint; using UQ112x112 for uint224; //定义了最小流动性。它是最小数值1的1000倍,用来在提供初始流动性时燃烧掉。 uint public constant MINIMUM_LIQUIDITY = 10**3; //来计算标准ERC20合约中转移代币函数transfer的函数选择器。虽然标准的ERC20合约在转移代币后返回一个成功值,但有些不标准的并没有返回值。在这个合约里统一做了处理,并使用了较低级的call函数代替正常的合约调用。函数选择器用于call函数调用中。 bytes4 private constant SELECTOR = bytes4(keccak256(bytes('transfer(address,uint256)')));
//用来记录factory合约地址和交易对中两种代币的合约地址。注意它们是public的状态变量,意味着合约外可以直接使用同名函数获取对应的值。 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; //记录某一时刻恒定乘积中积的值,主要用于开发团队手续费计算。 uint public kLast; // reserve0 * reserve1, as of immediately after the most recent liquidity event //这段代码是用来防重入攻击的,在modifier(函数修饰器)中,_;代表执行被修饰的函数体。所以这里的逻辑很好理解,当函数(外部接口)被外部调用时,unlocked设置为0,函数执行完之后才会重新设置为1。在未执行完之前,这时如果重入该函数,lock修饰器仍然会起作用。这时unlocked仍然为0,无法通过修饰器中的require检查,整个交易会被重置。当然这里也可以不用0和1,也可以使用布尔类型true和false。 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; }
//使用call函数进行代币合约transfer的调用(使用了函数选择器)。注意,它检查了返回值(首先必须调用成功,然后无返回值或者返回值为true)。 function _safeTransfer(address token, address to, uint value) private { (bool success, bytes memory data) = token.call(abi.encodeWithSelector(SELECTOR, to, value)); require(success && (data.length == 0 || abi.decode(data, (bool))), 'UniswapV2: TRANSFER_FAILED'); }
//接下来四个event定义是方便客户端进行各种追踪的。 //疑问:我部署的合约,别人可以追踪event吗? event Mint(address indexed sender, uint amount0, uint amount1); event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); event Swap( address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to ); event Sync(uint112 reserve0, uint112 reserve1);
//构造器,很简单,记录factory合约的地址。其实按照Solidity代码规范(建议),这里的构造器和它前面的四个event定义应该放在getReserves函数之前。 constructor() public { factory = msg.sender; } //进行合约的初始化。在第一篇核心合约源码学习中提到,因为factory合约使用create2函数创建交易对合约,无法向构造器传递参数,所以这里写了一个初始化函数用来记录合约中两种代币的地址。 // called once by the factory at time of deployment function initialize(address _token0, address _token1) external { require(msg.sender == factory, 'UniswapV2: FORBIDDEN'); // sufficient check token0 = _token0; token1 = _token1; }
// update reserves and, on the first call per block, price accumulators function _update(uint balance0, uint balance1, uint112 _reserve0, uint112 _reserve1) private { require(balance0 <= uint112(-1) && balance1 <= uint112(-1), 'UniswapV2: OVERFLOW'); uint32 blockTimestamp = uint32(block.timestamp % 2**32); uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired if (timeElapsed > 0 && _reserve0 != 0 && _reserve1 != 0) { // * never overflows, and + overflow is desired 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); }
//手续费计算,Uniswap开发团队为了避免这种情况的出现,将开发团队手续费累积起来,在改变流动性时才发送。 // 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) { //前两行用来获取开发团队手续费地址,并根据该地址是否为零地址来判断开关是否打开。 address feeTo = IUniswapV2Factory(factory).feeTo(); feeOn = feeTo != address(0); //第三行uint _kLast = kLast; //使用一个局部变量记录过去某时刻的恒定乘积中的积的值。注释表明使用局部变量可以减少gas(估计是因为减少了状态变量操作)。 uint _kLast = kLast; // gas savings //if(feeOn)语句,如果手续费开关打开,计算手续费的值(手续费以增发该交易对合约流动性代币的方式体现 if (feeOn) { if (_kLast != 0) { uint rootK = Math.sqrt(uint(_reserve0).mul(_reserve1)); uint rootKLast = Math.sqrt(_kLast); if (rootK > rootKLast) { uint numerator = totalSupply.mul(rootK.sub(rootKLast)); uint denominator = rootK.mul(5).add(rootKLast); uint liquidity = numerator / denominator; if (liquidity > 0) _mint(feeTo, liquidity); } } } else if (_kLast != 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);
bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee if (_totalSupply == 0) { liquidity = Math.sqrt(amount0.mul(amount1)).sub(MINIMUM_LIQUIDITY); _mint(address(0), MINIMUM_LIQUIDITY); // permanently lock the first MINIMUM_LIQUIDITY tokens } else { liquidity = Math.min(amount0.mul(_totalSupply) / _reserve0, amount1.mul(_totalSupply) / _reserve1); } require(liquidity > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED'); _mint(to, liquidity);
_update(balance0, balance1, _reserve0, _reserve1); 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 function burn(address to) external lock returns (uint amount0, uint amount1) { (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings address _token0 = token0; // gas savings address _token1 = token1; // gas savings uint balance0 = IERC20(_token0).balanceOf(address(this)); uint balance1 = IERC20(_token1).balanceOf(address(this)); uint liquidity = balanceOf[address(this)];
bool feeOn = _mintFee(_reserve0, _reserve1); uint _totalSupply = totalSupply; // gas savings, must be defined here since totalSupply can update in _mintFee amount0 = liquidity.mul(balance0) / _totalSupply; // using balances ensures pro-rata distribution amount1 = liquidity.mul(balance1) / _totalSupply; // using balances ensures pro-rata distribution require(amount0 > 0 && amount1 > 0, 'UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED'); _burn(address(this), liquidity); _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); if (feeOn) kLast = uint(reserve0).mul(reserve1); // reserve0 and reserve1 are up-to-date emit Burn(msg.sender, amount0, amount1, to); }
// this low-level function should be called from a contract which performs important safety checks function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock { require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT'); (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0; uint balance1; { // scope for _token{0,1}, avoids stack too deep errors address _token0 = token0; address _token1 = token1; require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO'); if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data); balance0 = IERC20(_token0).balanceOf(address(this)); balance1 = IERC20(_token1).balanceOf(address(this)); } uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0; uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0; require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT'); { // scope for reserve{0,1}Adjusted, avoids stack too deep errors uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3)); uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3)); require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K'); }
_update(balance0, balance1, _reserve0, _reserve1); emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } //函数,这里从注释就可以看出来,强制交易对合约中两种代币的实际余额和保存的恒定乘积中的资产数量一致(多余的发送给调用者)。注意:任何人都可以调用该函数来获取额外的资产(前提是如果存在多余的资产)。 // force balances to match reserves function skim(address to) external lock { address _token0 = token0; // gas savings address _token1 = token1; // gas savings _safeTransfer(_token0, to, IERC20(_token0).balanceOf(address(this)).sub(reserve0)); _safeTransfer(_token1, to, IERC20(_token1).balanceOf(address(this)).sub(reserve1)); } //和skim函数刚好相反,强制保存的恒定乘积的资产数量为交易对合约中两种代币的实际余额,用于处理一些特殊情况。通常情况下,交易对中代币余额和保存的恒定乘积中的资产数量是相等的。 // force reserves to match balances function sync() external lock { _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1); } }
|